Differences
This shows you the differences between the selected revisions of the page.
history 2022-09-12 | history 2025-06-06 09:29 (current) | ||
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.6]] 6.6 (not released yet) ((2025-06-06)) ===== |
- | 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; | + | * Inactive sessions can be automatically reconnected. [[bug>2232]] |
- | import androidx.appcompat.widget.Toolbar; | + | * Added dark theme support to: [[bug>1696]] |
- | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | + | * Login dialog. [[bug>2345]] |
+ | * Transfer Options dialog. | ||
+ | * Message boxes. | ||
+ | * Queue column headers. [[bug>2356]] | ||
+ | ····* Progress window. | ||
+ | * Authentication Progress window. [[bug>2358]] | ||
+ | * Bug fix: Scrollbar colors did not always reflect the color theme | ||
+ | * Installer upgraded to Inno Setup 6.4.2. | ||
+ | * Restoring ability to restart Explorer to allow upgrade of drag&drop shell extension, when installing for current user, as after-restart replacement is not possible without Administrator privileges. [[bug>2381]] | ||
+ | ··* MSI toolset updated to WiX 5. | ||
+ | * Commands to copy paths to the clipboard on the Synchronization checklist window. | ||
+ | * Consistently calling command to open window with specific directory //Explore//, instead of sometimes //Browse//. | ||
+ | * Consistently referring to file last modification timestamp column as //Date modified//. | ||
+ | * Control labels on transfer settings dialogs do not show keyboard accelerator cue, until ''Alt'' key is pressed. | ||
+ | * Bug fix: Local file with invalid characters replaced could not be explored from the Synchronization checklist window. | ||
+ | * Bug fix: Files modified by local custom command are not always uploaded to the correct remote directory. [[bug>2370]] | ||
- | import com.hmdm.control.janus.SharingEngineJanus; | + | ===== [[6.5.2]] 6.5.2 (not released yet) ((2025-06-04)) ===== |
- | public class MainActivity extends AppCompatActivity implements SharingEngineJanus.EventListener, SharingEngineJanus.StateListener { | + | * Translation updated: Polish. |
+ | * Bug fix: Some translations (notably Japanese) are not loaded. [[bug>2372]] | ||
+ | * Bug fix: Directory tree indentation is scaled incorrectly when starting on scaled display on system with scaled primary monitor. [[bug>2374]] | ||
+ | * Bug fix: Wrong icon size is used when starting on secondary monitor with different scaling than the primary one. | ||
+ | * Bug fix: Failure when proxy hostname resolution fails with SFTP/SCP protocols. [[bug>2376]] | ||
+ | * Bug fix: Avoid replacing ''%2F'' with a slash and ''%2E'' with a dot in special cases on upload to avoid path traversal. [[bug>2377]] | ||
+ | * Bug fix: Failure when canceling reconnection on authentication banner, when the connection was already closed by the server. [[bug>2379]] | ||
+ | * Bug fix: Local directories sometimes cannot be deleted. [[bug>2380]] | ||
- | ····private ImageView imageViewConnStatus; | + | ===== [[6.5.1]] 6.5.1 ((2025-05-05)) ===== |
- | ····private TextView textViewConnStatus; | + | |
- | ····private EditText editTextSessionId; | + | |
- | ····private EditText editTextPassword; | + | |
- | ···private TextView textViewComment; | + | |
- | ····private TextView textViewConnect; | + | |
- | ····private TextView textViewSendLink; | + | |
- | ···private TextView textViewExit; | + | |
- | ····private ImageView overlayDot; | + | ··* Translation completed: Danish. |
- | private Handler handler = new Handler(); | + | * Change: Skipping symlinks in //Search for Text// extension. [[bug>2365]] |
- | ···private int overlayDotAlpha; | + | * WebDAV/HTTP core upgraded to neon 0.34.2. It brings the following change, among other: |
- | ···private int overlayDotDirection = 1; | + | * Bug fix: Failure when parsing WebDAV ''<status>'' without reason phrase. [[bug>2368]] |
+ | ·* Bug fix: Monitor placement and maximization is not remembered on monitors with different scaling than the primary one. [[bug>2366]] | ||
+ | ·* Bug fix: WebDAV authentication is endlessly retried when both SSPI/NTLM/Negotiate and other authentication method fails. [[bug>2367]] | ||
+ | * Bug fix: Cannot incrementally search with ''space'' and cannot use ''space'' to toggle selection. [[bug>2364]] | ||
+ | * Bug fix: Failure after switching off //Thumbnails// view. [[bug>2369]] | ||
+ | * Bug fix: Failure when system app mode changes to //Dark//. | ||
- | ····private Dialog exitOnIdleDialog; | + | ===== [[6.5]] 6.5 ((2025-03-31)) ===== |
- | ···private int exitCounter; | + | |
- | ···private static final int EXIT_PROMPT_SEC = 10; | + | |
- | ····private static final int OVERLAY_DOT_ANIMATION_INCREMENT = 20; | + | ··* Translation completed: Norwegian. |
- | ···private static final int OVERLAY_DOT_ANIMATION_DELAY = 200; | + | * XML parser upgraded to Expat 2.7.1. |
+ | ·* Bug fix: Layout problems of by-default hidden columns on high-DPI displays. [[bug>2361]] | ||
+ | * Bug fix: Scaling was broken | ||
+ | * Bug fix: //Copy key fingerprints to clipboard// command link was scaled incorrectly. | ||
- | ····private SharingEngine sharingEngine; | + | ===== [[6.4.3]] 6.4.3 RC ((2025-03-17)) ===== |
- | ····private SettingsHelper settingsHelper; | + | ··* Display and modify S3 file/object tags. [[bug>2353]] |
+ | * Translations completed: Belarusian, Brazilian Portuguese, Catalan, Czech, Dutch, Finnish, French, German, Hungarian, Italian, Japanese, Korean, Polish, Portuguese, Romanian, Russian, Simplified Chinese, Slovak, Spanish, Swedish, Tamil, Traditional Chinese and Turkish; updated: Norwegian; and started: Georgian. | ||
+ | * SSH core and SSH private key tools (PuTTYgen and Pageant) upgraded to [[&url(puttychanges)|PuTTY 0.83]]. \\ It brings the following change: | ||
+ | * Bug fix: crash in Pageant if an SSH connection is abandoned while waiting for a deferred decryption passphrase. | ||
+ | * TLS/SSL core upgraded to OpenSSL 3.3.3. | ||
+ | * Installer upgraded to Inno Setup 6.4.1. | ||
+ | * XML parser upgraded to Expat 2.7.0. | ||
+ | * Allowed S3 connection with IAM roles on instances that require IMDSv2. [[bug>2351]] | ||
+ | * Remembering empty session password. [[bug>2352]] | ||
+ | * Small GUI improvements. | ||
+ | * Bug fix: Some parts of UI are not localized. [[bug>2346]] | ||
+ | * Bug fix: //Columns > Reset layout// command does not reset width of by-default hidden columns. [[bug>2347]] | ||
+ | * Bug fix: Some translation file were duplicitously interpreted as another additional language. | ||
+ | * Bug fix: Unicode contents in web sources was incorrectly decoded | ||
+ | * Bug fix: Intermediate translation ''.islu'' files were included in MSI installer. | ||
+ | * Bug fix: Monospace font is not remembered if SSH banners are administratively forced. | ||
+ | * Change: Bug fix: In XML log, ''filename'' tag was included in ''stat'' tag, although explicitly documented not to. | ||
+ | * Bug fix: Error when the last page of S3 directory listing is empty. [[bug>2355]] | ||
- | ····private String sessionId; | + | ===== [[6.4.2]] 6.4.2 beta ((2025-01-27)) ===== |
- | ···private String password; | + | |
- | ····private String adminName; | + | |
- | ····private final static String ATTR_SESSION_ID = "sessionId"; | + | ··* Synchronization actions can be pushed to background queue. [[bug>2318]] |
- | ···private final static String ATTR_PASSWORD = "password"; | + | * Ongoing local delete operation can be moved to a background queue (only on a remote tab, as local tabs do not have a queue). |
- | ···private final static String ATTR_ADMIN_NAME = "adminName"; | + | * Switching to //Segoe UI// font with slightly larger size (9) as recommended for Windows Vista and newer. All windows are now slightly larger accordingly and their layout was unified. |
+ | * SSH core upgraded to pre-release snapshot of PuTTY 0.83 (2025-01-03.1e45199). It brings the following change: | ||
+ | * Implement the post-quantum ML-KEM key encapsulation method. [[pbug>ml-kem]] | ||
+ | * SSH private key tools (PuTTYgen and Pageant) upgraded to PuTTY 0.82. | ||
+ | * Command-line interface for installing public key into server. [[bug>2342]] | ||
+ | * Installer upgraded to Inno Setup 6.4.0. | ||
+ | * Thumbnails for WebP images. [[bug>2336]] | ||
+ | * Change: Finding ''WinSCP.exe'' in a process path if a .NET assembly assembly path is unknown (before looking into an installation folder). [[bug>2339]] | ||
+ | ·* WebDAV/HTTP core upgraded to neon 0.34.0. | ||
+ | * Better error message when trying to open an inaccessible local drive (such as locked BitLocker drive). | ||
+ | * ''%%https://%%'' URL to S3 API at ''oraclecloud.com'' is interpreted as S3 protocol, instead of WebDAV. | ||
+ | * AWS metadata service connection timeout is by default one second and ''AWS_METADATA_SERVICE_TIMEOUT'' is respected. | ||
+ | * Added new ''ap-southeast-7'' and ''mx-central-1'' AWS regions. | ||
+ | * Bug fix: Hang when trying to open an inaccessible drive from drive drop down menu. | ||
+ | * Bug fix: On Windows 11, //Windows 10// version was logged. | ||
+ | * Bug fix: Hang when opening path with duplicated backslash. [[bug>2330]] | ||
+ | * Bug fix: When moving an ongoing delete operation to background queue, it might attempt to delete the current file again, failing the operation. | ||
+ | * Bug fix: After system theme change panel context menus stop working. [[bug>2331]] | ||
+ | * Bug fix: It was possible to start synchronization in new window despite incompatible //Selected files only// option selected by ''Shift''-clicking the //OK// button. [[bug>1870]] | ||
+ | ·* Bug fix: Failure when opening two SSH sessions at the same time. [[bug>2334]] | ||
+ | * Bug fix: Tunneled session password is not remembered. [[bug>2335]] | ||
+ | * Bug fix: Failure when trying to use a file not containing a private key as a client certificate file with WebDAV. | ||
+ | * Bug fix: ''Session.DebugLogPath'' cannot be set in single-file bundles. [[bug>2338]] | ||
+ | * Bug fix: Stray ''set'' command in source code package build script. [[bug>2340]] | ||
+ | * Bug fix: Duplicate shortcut key in local //Open directory// window for shared bookmarks. [[bug>710]] | ||
+ | * Bug fix: Source code package build script exits parent ''cmd'' console on error. [[bug>2341]] | ||
+ | * Bug fix: Command to //"Install public key into server"// was enabled, even when no hostname was specified | ||
- | ····private boolean needReconnect = false; | + | ===== [[6.4.1]] 6.4.1 beta ((2024-11-21)) ===== |
- | ····private MediaProjectionManager projectionManager; | + | ··* IDE upgraded to Embarcadero C++Builder 11. [[bug>618]] |
+ | * Change: Dropped support for Windows XP and Windows Vista. Minimal supported version is Windows 7. | ||
+ | * Improvements to Synchronization checklist window: | ||
+ | * Command to find //Move// candidate. | ||
+ | * //Move// command can be used with a set of orphaned files and new folder in source directory to create that directory on the target side and move the orphaned files there. | ||
+ | * Added //Calculate All// command. | ||
+ | * Added a keyboard shortcut to //Calculate// command. | ||
+ | * //Move// command can be used with long local paths. | ||
+ | * Compatibility with new OneDrive WebDAV interface. [[bug>2321]] | ||
+ | * Optimized loading of file panels when switching between tabs with different filters. [[bug>2315]] | ||
+ | * Optimized browsing deep local directory trees. | ||
+ | * XML parser upgraded to Expat 2.6.3. | ||
+ | * Including PuTTY and OpenSSL versions in ''/info''. | ||
+ | * Added new ''ap-southeast-5'' AWS region. | ||
+ | * Gracefully handling invalid Unicode characters FFFF and FFFE in filenames in .NET assembly. [[bug>2325]] | ||
+ | * Bug fix: Failure when system theme change is broadcasted in short order. | ||
+ | * Bug fix: Browsing long paths was broken. | ||
+ | * Bug fix: After leaving subdirectory, it is not scrolled into view in the parent directory. | ||
+ | * Bug fix: Long local panel location was lost after some file operations. | ||
+ | * Bug fix: Editor font was scaled twice on monitors with different DPI than the primary one. | ||
+ | * Bug fix: Option to switch to Location profiles was shown on Open directory dialog even when not possible, while session was disconnected. | ||
+ | * Bug fix: After delete operation is moved to background, some operations (notably editing) do not work anymore. [[bug>2319]] | ||
+ | * Bug fix: ''Shift+Ctrl+P'' keyboard shortcut on Login dialog to open site in PuTTY without closing the dialog does not work. [[bug>2320]] | ||
+ | * Bug fix: Failure when opening TLS connection with invalid OpenSSL configuration file. [[bug>2327]] | ||
+ | * Bug fix: Rare incorrect conversion of line endings when downloading. [[bug>2324]] | ||
+ | * Bug fix: Hang when creating temporary files with temporary folder on locked drive. [[bug>2328]] | ||
- | private BroadcastReceiver mSharingServiceReceiver = new BroadcastReceiver() { | + | ===== [[6.4]] 6.4 beta ((2024-09-19)) ===== |
- | @Override | + | |
- | public void onReceive(Context context, Intent intent) { | + | |
- | if (intent == null || intent.getAction() == null) { | + | |
- | ················return; | + | |
- | ···········} | + | |
- | if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_START)) { | + | |
- | ················notifySharingStart(); | + | |
- | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_STOP)) { | + | ··* Thumbnail view in file panels. [[bug>912]] |
- | ···············notifySharingStop(); | + | * Three selectable sizes of toolbar icons, showing slightly larger size by default. [[bug>2147]] |
- | ···············adminName = null; | + | * Optimized working with large local directories: [[bug>2264]] |
- | ···············updateUI(); | + | * Loading large directory trees in the background. |
- | ···············cancelSharingTimeout(); | + | ····* Hidden directory trees are loaded only in the background. |
- | ···············scheduleExitOnIdle(); | + | * Optimizing directory loading by checking existence of subfolders in the background. |
+ | ····* Optimized reading directories for file panel. | ||
+ | * Optimized browsing within directory tree with lots of subfolders. | ||
+ | ·* Dark theme for session tabs. [[bug>1806]] | ||
+ | ·* Improvements to AWS/S3 authentication and configuration handling: | ||
+ | * Allowed assuming IAM role. [[bug>2249]] | ||
+ | ···* Credential profiles in ''.aws/config'' file are also recognized. | ||
+ | * Preferring configuration file defined using an environment variable over the default one. | ||
+ | * Only profiles that contain both ''aws_access_key_id'' and ''aws_secret_access_key'' are listed on the Login dialog | ||
+ | * Bug fix: The ''.aws/credentials'' file path was resolved using ''AWS_CONFIG_FILE'' environment variable instead of correct ''AWS_SHARED_CREDENTIALS_FILE''. | ||
+ | * Incremental search improvements: | ||
+ | * Search panel pops up on ''Ctrl+F'' to make the function easier to find. [[bug>2281]] | ||
+ | ···* Layout of search panel on Login dialog improved. | ||
+ | * Search options context menu added to the search panel on Login dialog. | ||
+ | * Remembering panel scroll position when switching tabs or refreshing. [[bug>1057]] | ||
+ | ·* Search for Preferences options. | ||
+ | * TLS/SSL core upgraded to OpenSSL 3.3.2. | ||
+ | * Site import from an INI file. [[bug>2290]] | ||
+ | * Preserve timestamp when duplicating remote file using SFTP extension ''copy-data'' (OpenSSH). [[bug>2307]] | ||
+ | * Changing caret position on the first click on an inactive editor window. [[bug>2304]] | ||
+ | * Change: Limiting SFTP version to 3 with non-well-known SFTP servers as a workaround for interoperability issues. [[bug>2247]] | ||
+ | * Installer upgraded to Inno Setup 6.3.1. | ||
+ | * In icons view, files are rearranged automatically when the panel is resized. | ||
+ | * Explorer interface view style toolbar button now shows icon for the current style, instead of the next style. | ||
+ | * Fallback to nonresumable transfer when temporary target file cannot be created. [[bug>2277]] | ||
+ | * Optionally disabled smooth scrolling in an internal editor. [[bug>128]] | ||
+ | * Prevented redrawing scrollbars when loading remote directory tree. [[bug>2313]] | ||
+ | * Open window maximized when closed while minimized from maximized state. [[bug>2305]] | ||
+ | * Compatibility with FTP servers that return an empty path in root. [[bug>2300]] | ||
+ | * Ignoring WebDAV ''PROPFIND'' result entries that do not belong to the requested directory. [[bug>2312]] | ||
+ | * Optionally following ''NoViewOnDrive'' policy. [[bug>2310]] | ||
+ | * Recognizing matching Azure certificate files. [[bug>2311]] | ||
+ | * Logging can be enabled for installer subtasks. | ||
+ | * Limited SFTP upload queue length to avoid networking congestion. | ||
+ | * Updated to JCL library 2.8 commit 6380ce72. | ||
+ | * //Columns// menu in Explorer interface is enabled only in //Details// view. | ||
+ | * With ''/rawconfig'', session is never opened in an existing instance. | ||
+ | * Not including useless OpenSSL states in error reporting. | ||
+ | * Bug fix: Possibility that files in root folder were incorrectly used. | ||
+ | * Bug fix: Failure when system theme is changed at the same time directory change is detected. [[bug>2286]] | ||
+ | * Bug fix: Tree indentation does not scale correctly. [[bug>2288]] | ||
+ | * Bug fix: It was possible to start renaming a site by double-clicking its node while editing the site. | ||
+ | * Bug fix: Cannot download from WebDAV server when the request is redirected to the same path on another server. [[bug>2293]] | ||
+ | * Bug fix: Overlay images were not drawn in icons view when //Name// column in //Details// view was too narrow. | ||
+ | * Bug fix: Some Unicode texts, notably custom command names, were saved incorrectly in an INI file. [[bug>2301]] | ||
+ | * Bug fix: Caption of permissions group labels disappears when hovered over on Windows 11. | ||
- | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_FAILED)) { | + | ===== [[6.3.8]] 6.3.8 (not released yet) ((2025-06-04)) ===== |
- | 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)) { | + | ··* Back-propagated fixes from 6.4.3–6.5.2 releases: |
- | ···············sharingEngine.setState(Const.STATE_DISCONNECTED); | + | ···* Change: Skipping symlinks in //Search for Text// extension. [[bug>2365]] |
- | ················Toast.makeText(MainActivity.this, R.string.connection_failure_hint, Toast.LENGTH_LONG).show(); | + | ····* XML parser upgraded to Expat 2.7.0. |
- | ················updateUI(); | + | ····* Bug fix: Local directories sometimes cannot be deleted. [[bug>2380]] |
- | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED)) { | + | ===== [[6.3.7]] 6.3.7 ((2025-02-20)) ===== |
- | ················startActivityForResult(projectionManager.createScreenCaptureIntent(), Const.REQUEST_SCREEN_SHARE); | + | |
- | ···········} | + | |
- | } | + | |
- | ····}; | + | |
- | ····@Override | + | ··* Translation completed: Belarusian. |
- | protected void onCreate(Bundle savedInstanceState) { | + | * TLS/SSL core upgraded to OpenSSL 3.2.4. |
- | ·······super.onCreate(savedInstanceState); | + | * Back-propagated fixes and improvements from upcoming 6.4.2 beta release: |
- | ·······setContentView(R.layout.activity_main); | + | * Added new ''ap-southeast-7'' and ''mx-central-1'' AWS regions. |
+ | ···* Bug fix: Failure when opening two SSH sessions at the same time. [[bug>2334]] | ||
+ | ···* Bug fix: Tunneled session password is not remembered. [[bug>2335]] | ||
+ | * Bug fix: Stray ''set'' command in source code package build script. [[bug>2340]] | ||
+ | * Bug fix: Source code package build script exits parent ''cmd'' console on error. [[bug>2341]] | ||
- | ········if (savedInstanceState != null) { | + | ===== [[6.3.6]] 6.3.6 ((2024-11-25)) ===== |
- | ···········restoreInstanceState(savedInstanceState); | + | |
- | ·······} | + | |
- | ········Toolbar toolbar = findViewById(R.id.toolbar); | + | ··* Back-propagated improvements and fixes from 6.4.1 beta and 6.4.2 beta releases: |
- | ·······setSupportActionBar(toolbar); | + | ···* Compatibility with new OneDrive WebDAV interface. [[bug>2321]] |
- | ·······settingsHelper = SettingsHelper.getInstance(this); | + | ···* XML parser upgraded to Expat 2.6.3. |
- | ·······sharingEngine = SharingEngineFactory.getSharingEngine(); | + | ···* Added new ''ap-southeast-5'' AWS region. |
- | ·······sharingEngine.setEventListener(this); | + | ····* Bug fix: After delete operation is moved to background, some operations (notably editing) do not work anymore. [[bug>2319]] |
- | ·······sharingEngine.setStateListener(this); | + | ···* Bug fix: ''Shift+Ctrl+P'' keyboard shortcut on Login dialog to open site in PuTTY without closing the dialog does not work. [[bug>2320]] |
+ | ···* Bug fix: Hang when trying to open an inaccessible drive from drive drop down menu. | ||
+ | ··* Translations completed: Danish, and updated: Belarusian. | ||
- | ········DisplayMetrics metrics = new DisplayMetrics(); | + | ===== [[6.3.5]] 6.3.5 ((2024-09-10)) ===== |
- | ·······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); | + | ··* TLS/SSL core upgraded to OpenSSL 3.2.3. |
- | ·······sharingEngine.setScreenHeight(metrics.heightPixels); | + | ··* XML parser upgraded to Expat 2.6.3. |
+ | * Better error message when server is using incompatible TLS protocol version. [[bug>2299]] | ||
+ | ·* Translations updated: Belarusian and Finnish. | ||
+ | ··* Bug fix: Incorrect //"Credentials were not specified"// error when authentication fails due to incorrect password with FTP protocol. [[bug>2302]] | ||
+ | * Bug fix: Stall when switching from //S3// to other protocol on Login dialog on some networks. [[bug>2309]] | ||
- | ········IntentFilter intentFilter = new IntentFilter(Const.ACTION_SCREEN_SHARING_START); | + | ===== [[6.3.4]] 6.3.4 ((2024-06-17)) ===== |
- | 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); | + | ··* TLS/SSL core upgraded to OpenSSL 3.2.2. |
- | + | * Translations updated: Belarusian, Danish and Russian. | |
- | initUI(); | + | * Standalone executable installer can run over corrupted MSI installation. [[bug>2294]] |
- | setDefaultSettings(); | + | * Support for up-to 16KB WebDAV cookies. [[bug>2289]] |
- | } | + | * Bug fix: Failure when trying to automate file synchronization by checksum on an SFTP server that does not support it natively. [[bug>2291]] |
- | + | * Bug fix: Remote panel does not refresh after //"ZIP and Upload"//. [[bug>2292]] | |
- | @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~~ |