Differences
This shows you the differences between the selected revisions of the page.
history 2022-09-12 | history 2024-11-25 13:52 (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.4.2]] 6.4.2 (not released yet) ((2024-11-22)) ===== |
- | 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; | + | * Better error message when trying to open an inaccessible local drive (such as locked BitLocker drive). |
- | import androidx.appcompat.widget.Toolbar; | + | ··* Bug fix: Hang when trying to open an inaccessible drive from drive drop down menu. |
- | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | + | |
- | import com.hmdm.control.janus.SharingEngineJanus; | + | ===== [[6.4.1]] 6.4.1 beta ((2024-11-21)) ===== |
- | public class MainActivity extends AppCompatActivity implements SharingEngineJanus.EventListener, SharingEngineJanus.StateListener { | + | * 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 ImageView imageViewConnStatus; | + | ===== [[6.4]] 6.4 beta ((2024-09-19)) ===== |
- | ····private TextView textViewConnStatus; | + | |
- | ····private EditText editTextSessionId; | + | |
- | ···private EditText editTextPassword; | + | |
- | ····private TextView textViewComment; | + | |
- | ····private TextView textViewConnect; | + | |
- | ···private TextView textViewSendLink; | + | |
- | private TextView textViewExit; | + | |
- | ····private ImageView overlayDot; | + | ··* Thumbnail view in file panels. [[bug>912]] |
- | private Handler handler = new Handler(); | + | * Three selectable sizes of toolbar icons, showing slightly larger size by default. [[bug>2147]] |
- | private int overlayDotAlpha; | + | * Optimized working with large local directories: [[bug>2264]] |
- | private int overlayDotDirection = 1; | + | * Loading large directory trees on the background. |
+ | * Hidden directory trees are loaded only in the background. | ||
+ | * Optimizing directory loading by checking existence of subfolders on 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. | ||
- | ····private Dialog exitOnIdleDialog; | + | ===== [[6.3.6]] 6.3.6 ((2024-11-25)) ===== |
- | ···private int exitCounter; | + | |
- | ···private static final int EXIT_PROMPT_SEC = 10; | + | |
- | ····private static final int OVERLAY_DOT_ANIMATION_INCREMENT = 20; | + | ··* Back-propagated improvements and fixes from 6.4.1 beta and 6.4.2 beta releases: |
- | private static final int OVERLAY_DOT_ANIMATION_DELAY = 200; | + | * Compatibility with new OneDrive WebDAV interface. [[bug>2321]] |
+ | * XML parser upgraded to Expat 2.6.3. | ||
+ | * Added new ''ap-southeast-5'' AWS region. | ||
+ | * 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: Hang when trying to open an inaccessible drive from drive drop down menu. | ||
+ | * Translations completed: Danish, and updated: Belarusian. | ||
- | ····private SharingEngine sharingEngine; | + | ===== [[6.3.5]] 6.3.5 ((2024-09-10)) ===== |
- | ····private SettingsHelper settingsHelper; | + | ··* TLS/SSL core upgraded to OpenSSL 3.2.3. |
+ | * 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]] | ||
- | ····private String sessionId; | + | ===== [[6.3.4]] 6.3.4 ((2024-06-17)) ===== |
- | ···private String password; | + | |
- | ···private String adminName; | + | |
- | ····private final static String ATTR_SESSION_ID = "sessionId"; | + | ··* TLS/SSL core upgraded to OpenSSL 3.2.2. |
- | ···private final static String ATTR_PASSWORD = "password"; | + | * Translations updated: Belarusian, Danish and Russian. |
- | ···private final static String ATTR_ADMIN_NAME = "adminName"; | + | * Standalone executable installer can run over corrupted MSI installation. [[bug>2294]] |
+ | ·* 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]] | ||
- | ····private boolean needReconnect = false; | + | ===== [[6.3.3]] 6.3.3 ((2024-04-16)) ===== |
- | ····private MediaProjectionManager projectionManager; | + | ··* SSH core and SSH private key tools (PuTTYgen and Pageant) upgraded to [[&url(puttychanges)|PuTTY 0.81]]. \\ It brings the following change: |
+ | * Security fix for CVE-2024-31497: NIST P521/ecdsa-sha2-nistp521 signatures are no longer generated with biased values of //k//. The previous bias compromises private keys. [[bug>2285]] [[pbug>vuln-p521-bias]] | ||
+ | * Translation updated: Belarusian. | ||
+ | * XML parser upgraded to Expat 2.6.2. | ||
+ | * Support for TortoiseMerge in //Compare Files// extension. [[bug>2279]] | ||
+ | * Bug fix: File panel does not have focus after Login in Explorer interface. [[bug>2276]] | ||
+ | * Bug fix: Failure when closing the last remote tab. [[bug>2283]] | ||
+ | * Bug fix: Copy and paste to another application in Store installation sometimes does not work. [[bug>2284]] | ||
- | ····private BroadcastReceiver mSharingServiceReceiver = new BroadcastReceiver() { | + | ===== [[6.3.2]] 6.3.2 ((2024-03-12)) ===== |
- | @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)) { | + | ··* Translation updated: Belarusian. |
- | ···············notifySharingStop(); | + | * XML parser upgraded to Expat 2.6.1. |
- | ···············adminName = null; | + | ··* Optimized startup when right panel local directory tree is not visible. |
- | ···············updateUI(); | + | * Workaround for SFTP servers (Cisco) which omit message field from status response. [[bug>2272]] |
- | ···············cancelSharingTimeout(); | + | * Bug fix: Password pipe cannot be used to open a session in an existing instance. [[bug>2265]] |
- | ···············scheduleExitOnIdle(); | + | ·* Bug fix: Hang when canceling connection while reading remote directory. [[bug>2266]] |
+ | ·* Bug fix: Failure when canceling FTP connection while reading remote directory. [[bug>2267]] | ||
+ | ·* Bug fix: Cannot start on Windows XP. [[bug>2268]] | ||
+ | ·* Bug fix: Installation hangs when adding installation path to search path when executed in session 0. [[bug>2270]] | ||
+ | ·* Bug fix: Misplaced stored site use warning in scripting when session name is specified. [[bug>2271]] | ||
+ | * Bug fix: Correcting neon version in About box and logs. | ||
- | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_FAILED)) { | + | ===== [[6.3.1]] 6.3.1 ((2024-02-21)) ===== |
- | ···············String message = intent.getStringExtra(Const.EXTRA_MESSAGE); | + | ·* Translation completed: Norwegian. |
- | if (message != null) { | + | ··* Bug fix: Badly encoded SFTP packet when renaming a file using SFTP version 5 and newer. [[bug>2259]] |
- | ···················Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); | + | * Bug fix: Failure when trying to synchronize files by checksum on server that does not support it. [[bug>2260]] |
- | ················} | + | · * Bug fix: Random hang/failure when closing FTP TLS 1.3 connection. [[bug>2261]] |
- | ···············adminName = null; | + | ·* Bug fix: Cannot use IPv6 literal as hostname on Login dialog. [[bug>2263]] |
- | ···············updateUI(); | + | |
- | ···············cancelSharingTimeout(); | + | |
- | ················scheduleExitOnIdle(); | + | |
- | ············} else if (intent.getAction().equals(Const.ACTION_CONNECTION_FAILURE)) { | + | ===== [[6.3]] 6.3 ((2024-02-14)) ===== |
- | sharingEngine.setState(Const.STATE_DISCONNECTED); | + | |
- | Toast.makeText(MainActivity.this, R.string.connection_failure_hint, Toast.LENGTH_LONG).show(); | + | |
- | ···············updateUI(); | + | |
- | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED)) { | + | ··* XML parser upgraded to Expat 2.6.0. |
- | startActivityForResult(projectionManager.createScreenCaptureIntent(), Const.REQUEST_SCREEN_SHARE); | + | ··* Bug fix: Hang when prompt pops up while SFTP session is being reconnected. [[bug>2258]] |
- | ············} | + | |
- | } | + | |
- | }; | + | |
- | ····@Override | + | ===== [[6.2.4]] 6.2.4 RC ((2024-02-03)) ===== |
- | ····protected void onCreate(Bundle savedInstanceState) { | + | |
- | super.onCreate(savedInstanceState); | + | |
- | ········setContentView(R.layout.activity_main); | + | |
- | ········if (savedInstanceState != null) { | + | ··* Translations completed: Farsi, French, Japanese, Spanish and Traditional Chinese. |
- | ···········restoreInstanceState(savedInstanceState); | + | ·* TLS/SSL core upgraded to OpenSSL 3.2.1. |
- | ·······} | + | * WebDAV/HTTP core upgraded to neon 0.33.0. |
+ | * Bug fix: Failure when trying to upload file using double-click over disconnected session. [[bug>2254]] | ||
+ | * Bug fix: Failure after long frequent use of session tabs. [[bug>2255]] | ||
+ | * Bug fix: //New tab// icon is drawn incorrectly on Explorer interface with //Show selective text labels// turned off. [[bug>2257]] | ||
+ | * Bug fix: Failure when switching to another application while new session is being opened using //New Tab// tab. [[bug>2251]] | ||
- | ········Toolbar toolbar = findViewById(R.id.toolbar); | + | ===== [[6.2.3]] 6.2.3 RC ((2024-01-19)) ===== |
- | ·······setSupportActionBar(toolbar); | + | |
- | settingsHelper = SettingsHelper.getInstance(this); | + | |
- | ········sharingEngine = SharingEngineFactory.getSharingEngine(); | + | |
- | sharingEngine.setEventListener(this); | + | |
- | ·······sharingEngine.setStateListener(this); | + | |
- | ········DisplayMetrics metrics = new DisplayMetrics(); | + | ··* Added new ''ca-west-1'' AWS region. |
- | ·······ScreenSharingHelper.getRealScreenSize(this, metrics); | + | ·* Translations completed: Catalan, Czech, Dutch, Finnish, German, Hungarian, Italian, Korean, Polish, Portuguese, Romanian, Russian, Simplified Chinese, Slovak, Turkish and Tamil; and updated: Japanese. |
- | ·······float videoScale = ScreenSharingHelper.adjustScreenMetrics(metrics); | + | ·* Support for ''posix-rename@openssh.com'' SFTP extension. [[bug>2231]] |
- | ·······settingsHelper.setFloat(SettingsHelper.KEY_VIDEO_SCALE, videoScale); | + | ·* When cleaning up application data, deleting even ''Martin Prikryl'' and ''WinSCP 2'' root keys, if they remain empty. |
- | ·······ScreenSharingHelper.setScreenMetrics(this, metrics.widthPixels, metrics.heightPixels, metrics.densityDpi); | + | * Ignoring attempts to directly move/duplicate file over itself, as if protocol requires deleting, the file would be lost. |
+ | * Configurable FTP TLS shutdown procedure. [[bug>2250]] | ||
+ | ·* Not failing connection when FTP server responds to ''CSID'' command with an error. [[bug>2253]] | ||
+ | · * Bug fix: Certificate authority cache was not copied to new configuration storage nor cleaned up with other caches. | ||
+ | ··* Bug fix: ''ssh'' protocol URL handling was not completely unregistered. | ||
+ | ··* Bug fix: Reported transfer size is rarely incorrect during FTP downloads. | ||
+ | ··* Bug fix: Failure after connecting to server. [[bug>2251]] | ||
+ | * Bug fix: FTP ''CSID'' command does not end with semicolon. [[bug>2252]] | ||
- | ········sharingEngine.setScreenWidth(metrics.widthPixels); | + | ===== [[6.2.2]] 6.2.2 beta ((2023-12-22)) ===== |
- | ·······sharingEngine.setScreenHeight(metrics.heightPixels); | + | |
- | ········IntentFilter intentFilter = new IntentFilter(Const.ACTION_SCREEN_SHARING_START); | + | ··* SSH core and SSH private key tools (PuTTYgen and Pageant) upgraded to [[&url(puttychanges)|PuTTY 0.80]]. \\ It brings the following change: |
- | ·······intentFilter.addAction(Const.ACTION_SCREEN_SHARING_STOP); | + | ···* Mitigations for SSH protocol "Terrapin" vulnerability. [[bug>2246]] [[pbug>vuln-terrapin]] |
- | ·······intentFilter.addAction(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED); | + | ·* Support for ''Include'' directive when importing sites from OpenSSH. [[bug>2239]] |
- | ·······intentFilter.addAction(Const.ACTION_SCREEN_SHARING_FAILED); | + | ·* Change: .NET assembly collections are tagged with ''ClassInterfaceType.None'' to avoid warnings from ''regasm''. |
- | ·······intentFilter.addAction(Const.ACTION_CONNECTION_FAILURE); | + | ·* Not using directory listing to keep FTP session alive by default. [[bug>2244]] |
- | ·······LocalBroadcastManager.getInstance(this).registerReceiver(mSharingServiceReceiver, intentFilter); | + | * Windows Store installation on Windows 11 was incorrectly using INI file for configuration storage by default. [[bug>2245]] |
+ | ·* Bug fix: Find dialog file list is scaled incorrectly on some multi monitor systems with different scaling. [[bug>2241]] | ||
+ | · * Bug fix: Cannot browse long local paths. [[bug>2242]] | ||
- | ········projectionManager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE); | + | ===== [[6.2.1]] 6.2.1 beta ((2023-12-05)) ===== |
- | ········initUI(); | + | ··* File hash can be used as criterion for synchronization. [[bug>52]] |
- | setDefaultSettings(); | + | * Consistent behavior across protocols and protocol capabilities when duplicating remote files. [[bug>2233]] |
- | } | + | * //Columns > Reset Layout// command added to Explorer interface too. |
- | + | * TLS/SSL core upgraded to OpenSSL 3.2.0. | |
- | @Override | + | * Support for "Requester pays" S3 buckets. [[bug>2213]] |
- | protected void onResume() { | + | * Optional more prominent active session tab. [[bug>2229]] |
- | super.onResume(); | + | * Optionally not shortening tab titles. [[bug>2202]] |
- | updateUI(); | + | * New ''winscp.net'' root certificate. |
- | + | * Restored support for legacy version of the Digest algorithm specified in RFC 2069. [[bug>2109]] | |
- | startService(new Intent(MainActivity.this, GestureDispatchService.class)); | + | * Restored consistent behavior of failing, between duplicating and moving/renaming files over existing file with WebDAV protocol in scripting and .NET assembly. |
- | // connect(); | + | * When moving a folder by drag&drop to a path that already contains a subfolder with the same name, the existing folder is overwritten. |
- | checkAccessibility(); | + | * Shorter and more friendly formatting of long time intervals. [[bug>2236]] |
- | } | + | * When typing ambiguous port numbers in Login dialog, keeping the current protocol, even if it is not the default protocol for the port. |
- | + | * Bug fix: Failure when //New Tab// is clicked while another session is still being loaded. | |
- | private void checkAccessibility() { | + | * Bug fix: Corrected some painting artifacts on session tabs, particularly on Windows 11. |
- | if (!Utils.isAccessibilityPermissionGranted(this)) { | + | * Bug fix: ''Shift+F5'' shortcut operated with a focused file, rather than with selected files. |
- | textViewConnect.setVisibility(View.INVISIBLE); | + | * Bug fix: OpenSSL version in About dialog was not up to date. |
- | new AlertDialog.Builder(this) | + | * Bug fix: Cannot leave directory entered via cache with SCP protocol if it was deleted meanwhile. [[bug>2234]] |
- | .setMessage(R.string.accessibility_hint) | + | * Bug fix: Failure when connection is lost while reading remote directory with SFTP protocol. [[bug>2235]] |
- | .setPositiveButton(R.string.continue_button, new DialogInterface.OnClickListener() { | + | * Bug fix: Multipart upload to Cloudflare R2 S3 interface fails due to too long upload ID. [[bug>2237]] |
- | @Override | + | * Bug fix: Panel focus was lost in some situations. |
- | public void onClick(DialogInterface dialog, int which) { | + | * Bug fix: When S3 or WebDAV server did not provide file timestamp, downloaded file was set to oldest possible timestamp. |
- | Intent intent = new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS); | + | * Bug fix: When session URL is typed into //Host name// box or pasted using context menu of the box and the Login dialog is submitted using ''Enter'' key, the URL is not parsed correctly. |
- | try { | + | * Bug fix: Failure when saving edited file over reconnected session after previous reconnect attempt was aborted. [[bug>2238]] |
- | 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~~ |