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) | Restored revision 1662982066. Undoing revisions 1662989532, 1662994511, 1662994524, 1662994533, 1662994543, 1662994553, 1662994582, 1662994597, 1662994624. (martin) (hidden) | ||
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. | + | |
- | */ | + | |
- | package com.hmdm.control; | + | 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]]. |
- | import android.app.AlertDialog; | + | ===== [[6.0]] 6.0 (not released yet) ((2022-09-07)) ===== |
- | import android.app.Dialog; | + | ··* Local file manager mode (two local panels). [[bug>1893]] |
- | import android.content.BroadcastReceiver; | + | ··* Ongoing delete operation can be moved to background queue. [[bug>194]] |
- | import android.content.ClipData; | + | ··* Showing directory size in file panel. [[bug>41]] |
- | import android.content.ClipboardManager; | + | ··* File checksum calculation support for SCP protocol and SFTP protocol via secondary shell session using shell commands like ''sha256sum''. |
- | import android.content.Context; | + | ··* Tab titles are shortened to fit window width as needed. [[bug>1423]] |
- | import android.content.DialogInterface; | + | ··* Tabs show tooltips with full session name and paths. |
- | import android.content.Intent; | + | * Displaying thanks and transitioning help toolbar message after Store installation over classic installation. |
- | import android.content.IntentFilter; | + | ··* Remembering remote directory tree nodes state when switching sessions. [[bug>1057]] |
- | import android.graphics.PixelFormat; | + | ··* Support for S3 servers without TLS encryption. [[bug>1995]] |
- | import android.media.projection.MediaProjectionManager; | + | ··* Support for redirected WebDAV downloads (even to other hosts). [[bug>1667]] |
- | import android.os.Build; | + | ··* ''Shift''-clicking //New Session// command opens the Login dialog in new WinSCP instance, instead of possibly opening new instance of workspace. |
- | import android.os.Bundle; | + | * Allowed opening all sites in a folder in PuTTY. [[bug>2079]] |
- | import android.os.Handler; | + | ··* Allowing environment variables in custom INI file path. [[bug>2105]] |
- | import android.util.DisplayMetrics; | + | ··* Private key pattern ''!K'' in PuTTY command-line and custom commands. [[bug>2107]] |
- | import android.util.Log; | + | ··* Automatically reconnect when FTP server fails to open data connection with ''426'' code, if it previously worked. [[bug>2110]] |
- | import android.view.Gravity; | + | ··* Cleaning up temporary WinSCP PuTTY sessions. |
- | import android.view.Menu; | + | * Added import from KiTTY directly to the Import dialog. [[bug>1551]] |
- | import android.view.MenuItem; | + | ··* Blocking Windows Rich Edit formatting keyboard shortcuts in Internal editor. [[bug>2108]] |
- | import android.view.View; | + | ··* PNG code upgraded to PngComponents 1.9.0. |
- | import android.view.WindowManager; | + | ··* When switching tabs, prevent visibly scrolling the panels when focusing the last selected file. |
- | import android.widget.EditText; | + | ··* Removed the "compression" indicator from the status bar. |
- | import android.widget.ImageView; | + | * Allowed normal behavior of double-click on a file even when resolving of symlinks is disabled. [[bug>2037]] |
- | import android.widget.TextView; | + | ··* Change: ''SessionOptions.WebdavSecure'' renamed to ''SessionOptions.Secure'' (and applies to S3 protocol too). |
- | import android.widget.Toast; | + | ··* ''Session.ExecutablePath'' returns detected or actual executable path when not set. [[bug>2055]] |
+ | ··* Change: Keyboard shortcut for //Command Line// command changed to ''Shift+Ctrl+M'' (''Shift+Ctrl+N'' previously). | ||
+ | ··* Preventing construction of .NET assembly internal collection classes, what avoids them being unnecessarily registered for COM. | ||
+ | ··* Bug fix: Failure when trying to switch session tabs while previous switch did not complete yet. | ||
+ | * Bug fix: When importing from FileZilla with KiTTY selected as SSH terminal, host keys were imported from KiTTY instead of FileZilla/PuTTY. | ||
+ | ··* Bug fix: Incorrect escaping of values in single-quoted patterns in custom commands. | ||
- | import androidx.appcompat.app.AppCompatActivity; | + | ===== [[5.21.4]] 5.21.4 ((2022-09-12)) ===== |
- | import androidx.appcompat.widget.Toolbar; | + | |
- | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | + | |
- | import com.hmdm.control.janus.SharingEngineJanus; | + | * This is Microsoft Store-only release with the following fix: |
+ | ····* The //Donate// command removed from the //Help// menu to comply with Store App policies. | ||
- | public class MainActivity extends AppCompatActivity implements SharingEngineJanus.EventListener, SharingEngineJanus.StateListener { | + | ===== [[5.21.3]] 5.21.3 ((2022-09-06)) ===== |
- | ····private ImageView imageViewConnStatus; | + | ··* Translation updated: Ukrainian. |
- | ···private TextView textViewConnStatus; | + | ·* Added new ''me-central-1'' AWS region. |
- | ····private EditText editTextSessionId; | + | ·* Bug fix: Site with a stored password protected by master password cannot be opened from command-line. [[bug>2101]] |
- | ···private EditText editTextPassword; | + | ·* Bug fix: Opening session in new window fails. [[bug>2104]] |
- | private TextView textViewComment; | + | |
- | ····private TextView textViewConnect; | + | |
- | ···private TextView textViewSendLink; | + | |
- | private TextView textViewExit; | + | |
- | ····private ImageView overlayDot; | + | ===== [[5.21.2]] 5.21.2 ((2022-08-08)) ===== |
- | ····private Handler handler = new Handler(); | + | |
- | ····private int overlayDotAlpha; | + | |
- | ····private int overlayDotDirection = 1; | + | |
- | ····private Dialog exitOnIdleDialog; | + | ··* Translation completed: Farsi. |
- | ···private int exitCounter; | + | * TLS/SSL core upgraded to OpenSSL 1.1.1q. |
- | ···private static final int EXIT_PROMPT_SEC = 10; | + | * Initial implementation of application logging. |
+ | * Bug fix: "Hidden files showing toggled" message displays opposite status. | ||
+ | ·* Bug fix: Cannot authenticate to WebDAV server with username containing some special characters. [[bug>2095]] | ||
+ | ·* Bug fix: Checkbox to set permissions recursively on Properties dialog is missing, when changing owner and group is not supported. [[bug>2097]] | ||
+ | * Bug fix: .NET assembly type library cannot be used in WSH. [[bug>2098]] | ||
- | ····private static final int OVERLAY_DOT_ANIMATION_INCREMENT = 20; | + | ===== [[5.21.1]] 5.21.1 ((2022-06-24)) ===== |
- | ···private static final int OVERLAY_DOT_ANIMATION_DELAY = 200; | + | · * Translation completed: Polish. |
+ | * TLS/SSL core upgraded to OpenSSL 1.1.1p. | ||
+ | * Not offering to create a link to parent directory, when it happens to be selected, when starting to create a link. [[bug>2091]] | ||
+ | ·* Bug fix: Extension options dialog layout broken on systems with multiple monitors with different text scaling. [[bug>2090]] | ||
+ | * Bug fix: Failure when starting parallel FTP background transfers. [[bug>2093]] | ||
+ | * Bug fix: Directory coloring rules on remote panel do not work. [[bug>2094]] | ||
- | ····private SharingEngine sharingEngine; | + | ===== [[5.21]] 5.21 ((2022-06-15)) ===== |
- | ····private SettingsHelper settingsHelper; | + | ··* Bug fix: Hang when entering small directories with FTP protocol. [[bug>2087]] |
+ | * Bug fix: Lag when moving files from remote panel using drag&drop. [[bug>2088]] | ||
+ | * Bug fix: After certain operations, drive that was ever opened in local file panel cannot be safely removed temporarily. | ||
- | ····private String sessionId; | + | ===== [[5.20.4]] 5.20.4 RC ((2022-06-08)) ===== |
- | private String password; | + | |
- | private String adminName; | + | * 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]] | ||
- | ····private final static String ATTR_SESSION_ID = "sessionId"; | + | ===== [[5.20.3]] 5.20.3 RC ((2022-05-17)) ===== |
- | ···private final static String ATTR_PASSWORD = "password"; | + | |
- | ···private final static String ATTR_ADMIN_NAME = "adminName"; | + | |
- | ····private boolean needReconnect = false; | + | ··* 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 |
+ | * 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]] | ||
- | ····private MediaProjectionManager projectionManager; | + | ===== [[5.20.2]] 5.20.2 beta ((2022-04-06)) ===== |
- | ····private BroadcastReceiver mSharingServiceReceiver = new BroadcastReceiver() { | + | ··* Translations updated: German and Vietnamese. |
- | ·······@Override | + | * 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: |
- | ·······public void onReceive(Context context, Intent intent) { | + | ···* Support for HTTP Digest authentication for proxies. [[pbug>http-digestauth]] |
- | ···········if (intent == null || intent.getAction() == null) { | + | ···* Interactive username/password prompts for proxy authentication. [[bug>468]] [[pbug>proxy-password-prompt]] |
- | ···············return; | + | · * TLS/SSL core upgraded to OpenSSL 1.1.1n. |
- | ···········} | + | ·* WebDAV core upgraded to neon 0.32.2. |
- | ···········if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_START)) { | + | · * XML parser upgraded to Expat 2.4.8. |
- | ···············notifySharingStart(); | + | · * 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]] | ||
- | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_STOP)) { | + | ===== [[5.20.1]] 5.20.1 beta ((2022-01-11)) ===== |
- | ···············notifySharingStop(); | + | ·* File masks relative to the root of an operation. [[bug>2052]] |
- | ···············adminName = null; | + | ·* Private key can be provided as string in .NET assembly and scripting. [[bug>2044]] |
- | ···············updateUI(); | + | ·* Support for importing key files that are specified using home ''~'' prefix from OpenSSH ''config'' file. [[bug>2053]] |
- | ···············cancelSharingTimeout(); | + | ·* Translation updated: German. |
- | ···············scheduleExitOnIdle(); | + | * 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. | ||
- | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_FAILED)) { | + | ===== [[5.20]] 5.20 beta ((2021-12-02)) ===== |
- | String message = intent.getStringExtra(Const.EXTRA_MESSAGE); | + | |
- | if (message != null) { | + | |
- | Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); | + | |
- | ···············} | + | |
- | ················adminName = null; | + | |
- | updateUI(); | + | |
- | cancelSharingTimeout(); | + | |
- | scheduleExitOnIdle(); | + | |
- | ············} else if (intent.getAction().equals(Const.ACTION_CONNECTION_FAILURE)) { | + | ··* Support for ACL for S3 protocol. [[bug>1641]] |
- | ···············sharingEngine.setState(Const.STATE_DISCONNECTED); | + | * SSH core upgraded to [[&url(puttychanges)|PuTTY 0.76]]. [[bug>1984]] \\ It brings the following changes: |
- | ···············Toast.makeText(MainActivity.this, R.string.connection_failure_hint, Toast.LENGTH_LONG).show(); | + | ···* Support Curve448 key exchange method. [[pbug>curve448]] |
- | ···············updateUI(); | + | * Support Ed448 user and host keys. [[pbug>ed448]] |
+ | ···* Support rsa-sha2-256 and rsa-sha2-512 SSH public key algorithms. [[bug>1952]] [[pbug>rsa-sha2]] | ||
+ | * Change: SHA-256 fingerprints are not padded anymore. | ||
+ | * It is possible to import sessions from OpenSSH ''config'' file. [[bug>1896]] | ||
+ | * Change: Removed support for SSH-1. | ||
+ | * 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. | ||
- | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED)) { | + | ===== [[5.19.6]] 5.19.6 (hotfix) ((2022-02-22)) ===== |
- | startActivityForResult(projectionManager.createScreenCaptureIntent(), Const.REQUEST_SCREEN_SHARE); | + | |
- | ············} | + | |
- | ·······} | + | |
- | ····}; | + | |
- | ····@Override | + | ··* Translation updated: German. |
- | protected void onCreate(Bundle savedInstanceState) { | + | * Back-propagated fixes from 5.20–5.20.2 releases: |
- | super.onCreate(savedInstanceState); | + | ···* TLS/SSL core upgraded to OpenSSL 1.1.1m. |
- | ·······setContentView(R.layout.activity_main); | + | * XML parser upgraded to Expat 2.4.6. |
- | ········if (savedInstanceState != null) { | + | ===== [[5.19.5]] 5.19.5 ((2021-11-25)) ===== |
- | ···········restoreInstanceState(savedInstanceState); | + | |
- | ·······} | + | |
- | ········Toolbar toolbar = findViewById(R.id.toolbar); | + | ··* Compatibility with Google Cloud S3 API when duplicating files. [[bug>2038]] |
- | ·······setSupportActionBar(toolbar); | + | ·* Compatibility with Google Cloud S3 API when deleting implicitly existing directories. [[bug>2042]] |
- | ·······settingsHelper = SettingsHelper.getInstance(this); | + | ·* Translation updated: Turkish. |
- | ·······sharingEngine = SharingEngineFactory.getSharingEngine(); | + | ··* Logging a reference to [[bug>1952]] when an OpenSSH 8.8 (or newer) server refuses the key. |
- | ·······sharingEngine.setEventListener(this); | + | ·* Bug fix: Crash when new contents is copied to the clipboard while downloading files pasted from the clipboard. [[bug>2036]] |
- | ·······sharingEngine.setStateListener(this); | + | ·* 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. | ||
- | ········DisplayMetrics metrics = new DisplayMetrics(); | + | ===== [[5.19.4]] 5.19.4 ((2021-10-24)) ===== |
- | ·······ScreenSharingHelper.getRealScreenSize(this, metrics); | + | |
- | float videoScale = ScreenSharingHelper.adjustScreenMetrics(metrics); | + | |
- | settingsHelper.setFloat(SettingsHelper.KEY_VIDEO_SCALE, videoScale); | + | |
- | ScreenSharingHelper.setScreenMetrics(this, metrics.widthPixels, metrics.heightPixels, metrics.densityDpi); | + | |
- | ········sharingEngine.setScreenWidth(metrics.widthPixels); | + | ··* Translation updated: Hungarian. |
- | ·······sharingEngine.setScreenHeight(metrics.heightPixels); | + | ··* Showing release date on the About dialog. |
+ | * 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. | ||
- | ········IntentFilter intentFilter = new IntentFilter(Const.ACTION_SCREEN_SHARING_START); | + | ===== [[5.19.3]] 5.19.3 ((2021-10-11)) ===== |
- | 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); | + | ··* Translation updated: French. |
- | + | * TLS/SSL core upgraded to OpenSSL 1.1.1l. | |
- | initUI(); | + | * Using //Documents// folder when the last used local directory in Explorer interface does not exist anymore. [[bug>2011]] |
- | setDefaultSettings(); | + | * 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]] |
- | } | + | * Bug fix: Cannot access S3 bucket root when the access policy checks for empty prefix. [[bug>2021]] |
- | + | * Bug fix: Response from ProFTPD FTP checksum commands is not recognized. [[bug>2023]] | |
- | @Override | + | * Bug fix: Failure when submitting prompt with //"Never ask me again"// selected. [[bug>2022]] |
- | protected void onResume() { | + | * Bug fix: Panels are drawn incorrectly after toggling //Full row select//. [[bug>2025]] |
- | super.onResume(); | + | * Bug fix: Timeout while uploading files to some FTP servers using TLS 1.3. [[bug>2030]] |
- | updateUI(); | + | * 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]] |
- | + | ||
- | ·······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~~ |