Differences
This shows you the differences between the selected revisions of the page.
| history 2022-09-12 | history 2026-08-08 (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.3]] 6.6.3 (not released yet) ((2026-08-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; | + | * Translations completed: Belarusian, Brazilian Portuguese, Catalan, Croatian, Czech, Danish, Dutch, Finnish, French, German, Hungarian, Italian, Japanese, Korean, Lithuanian, Macedonian, Polish, Portuguese, Romanian, Russian, Serbian, Simplified Chinese, Slovak, Slovenian, Spanish, Swedish, Tamil, Traditional Chinese and Turkish. |
| - | import androidx.appcompat.widget.Toolbar; | + | * XML parser upgraded to Expat 2.8.2. |
| - | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | + | ··* Source code package build script supports 64-bit target. |
| + | * 64-bit build is identified in version information. | ||
| + | * Resolving version of pwsh installed with MSIX. | ||
| + | ··* Thirdparty information from the About dialog can be copied to the clipboard even when the browser control malfunctions. | ||
| + | ··* Bug fix: No error is shown when connection fails. | ||
| + | * Bug fix: ''x-name'' URL parameter was incorrectly decoded | ||
| + | * Bug fix: Incorrect number validation. | ||
| + | * Bug fix: Some controls (notably list view headers) do not correctly apply dark mode in 64-bit build. [[bug>2453]] | ||
| - | import com.hmdm.control.janus.SharingEngineJanus; | + | ===== [[6.6.2]] 6.6.2 RC ((2026-06-17)) ===== |
| - | public class MainActivity extends AppCompatActivity implements SharingEngineJanus.EventListener, SharingEngineJanus.StateListener { | + | * Experimental 64-bit version of WinSCP. [[bug>618]] |
| + | * Optionally not showing error message when connection is lost while idle. [[bug>2360]] | ||
| + | * SSH core and SSH private key tools (PuTTYgen and Pageant) upgraded to [[&url(puttychanges)|PuTTY 0.84]]. \\ It brings the following change: | ||
| + | * Security issue: fixed a remotely triggerable double-free in RSA key exchange. [[pbug>rsakex-double-free]] | ||
| + | * Minor security issue: fixed a remotely triggerable crash in NIST ECDSA signature verification. [[pbug>ecdsa-remotely-triggerable-assertion]] | ||
| + | * Bug fix: spurious //"Network error: Socket is not connected"// when authenticating to some HTTP proxies. [[pbug>http-proxy-auth-wsaenotconn]] | ||
| + | * TLS/SSL core upgraded to OpenSSL 3.5.7. | ||
| + | * XML parser upgraded to Expat 2.8.1. | ||
| + | * Restored faster C TLS/SSL AES implementation. | ||
| + | * Configurable warning when opening large file in an internal editor. [[bug>2437]] | ||
| + | * Informing that when preserving directory timestamps is enabled, using multiple connections for transfer is not possible. [[bug>2439]] | ||
| + | * Warning when pasting a session URL with unsafe settings. | ||
| + | * When opening session in PuTTY to a host for which WinSCP has multiple host keys cached, using the last key or the key that PuTTY has cached. [[bug>2440]] | ||
| + | * Always (re)registering drag&drop shell extension during installation, even when the extension is not replaced. | ||
| + | * Allowed Console interface tool to have ''.exe'' extension to avoid false positive detections by some antiviruses. [[bug>2434]] | ||
| + | * Using //"username"// and //"hostname"// as one word. | ||
| + | * Reading all system settings from 64-bit registry. | ||
| + | * Allow assigning ''null'' to ''Session.SessionLogPath''. [[bug>2438]] | ||
| + | * Avoiding using ''SSH_FXF_EXCL'' together with ''SSH_FXF_TRUNC'' SFTP file opening flags. [[bug>2444]] | ||
| + | * Optimized file system monitoring when looking for dummy directory during drag&drop downloads. [[bug>2445]] | ||
| + | * Change: Not allowing WebDAV redirects to other hosts by default. [[bug>2447]] | ||
| + | * Change: Not allowing WebDAV redirects to an unencrypted URL by default. [[bug>2448]] | ||
| + | * Updated to JCL library 2.9 commit c669fd12. | ||
| + | * Bug fix: Failure when trying to connect via HTTP proxy to FTP host with excessively long login details. [[bug>2435]] | ||
| + | * Bug fix: Buffer overflow in Console interface tool. [[bug>2436]] | ||
| + | * Bug fix: Failure setting ''Session.DebugLogPath'' when running in impersonated context. [[bug>2441]] | ||
| + | * Bug fix: Message boxes from secondary windows (like the internal editor) caused application to move to the background when when the main window was minimized. [[bug>2443]] | ||
| + | * Bug fix: Heap over-read via crafted encrypted filename. [[bug>2449]] | ||
| + | * Bug fix: Slashes in filenames can cause path traversal when invalid filename characters replacement is disabled. [[bug>2450]] | ||
| - | ····private ImageView imageViewConnStatus; | + | ===== [[6.6.1]] 6.6.1 beta ((2026-04-01)) ===== |
| - | ····private TextView textViewConnStatus; | + | |
| - | ····private EditText editTextSessionId; | + | |
| - | ····private EditText editTextPassword; | + | |
| - | ···private TextView textViewComment; | + | |
| - | ····private TextView textViewConnect; | + | |
| - | ····private TextView textViewSendLink; | + | |
| - | ···private TextView textViewExit; | + | |
| - | ····private ImageView overlayDot; | + | ··* Support for OpenSSH ssh-agent. [[bug>1682]] |
| - | ···private Handler handler = new Handler(); | + | ·* Optionally connecting all workspace/folder sessions immediately. [[bug>1026]] |
| - | ···private int overlayDotAlpha; | + | ·* Preserving panel scroll position after rename. [[bug>2425]] |
| - | ···private int overlayDotDirection = 1; | + | ·* ''Ctrl+C'' works in list views on 'Server and protocol information' dialog. |
| + | * Preventing moving or copying a file or folder over ancestor folder with the same name. [[bug>2427]] | ||
| + | * WebDAV/HTTP core upgraded to neon 0.37.1. | ||
| + | * XML parser upgraded to Expat 2.7.5. | ||
| + | * Bug fix: Some menus were not working on displays to the left or above the primary display. [[bug>2423]] | ||
| + | * Bug fix: Mouse wheel downwards scrolling did not work on toolbar drop down lists. | ||
| + | * Bug fix: Once any control of permissions popup box was focused the popup no longer closed when user clicked outside of it. | ||
| + | * Bug fix: Failure when closing Transfer settings dialog with //X// button while a permissions popup box control is focused. [[bug>2420]] | ||
| + | * Bug fix: Failure when switching to a session that is being reconnected. | ||
| + | * Bug fix: Failure when the first bit of an SFTP response is set. [[bug>2422]] | ||
| + | * Bug fix: Copying to clipboard with ''Ctrl+C'' from 'Server and protocol information' was broken. | ||
| + | * Bug fix: Protocol additional information scrolling was broken. | ||
| + | * Bug fix: Master password dialog was missing //Help// button. | ||
| + | * Bug fix: Checking if edited/opened file was modified externally didn't work for inactive sessions. [[bug>2426]] | ||
| + | * Bug fix: Wrapped settings values from Raw Site Settings dialog were not preserved. | ||
| + | * Bug fix: Some files modified by local custom command in SCP session fail to upload back. [[bug>2428]] | ||
| + | * Bug fix: Whole //Key exchange// page was incorrectly hidden when //"Handles SSH key re-exchange badly"// bug was enabled. | ||
| + | * Bug fix: Some message boxes leak GDI handle. [[bug>2430]] | ||
| + | * Bug fix: Login dialog leaks GDI handles. [[bug>2431]] | ||
| - | ····private Dialog exitOnIdleDialog; | + | ===== [[6.6]] 6.6 beta ((2026-02-02)) ===== |
| - | ···private int exitCounter; | + | |
| - | ····private static final int EXIT_PROMPT_SEC = 10; | + | |
| - | ····private static final int OVERLAY_DOT_ANIMATION_INCREMENT = 20; | + | ··* Synchronizing two local directories. [[bug>2020]] |
| - | private static final int OVERLAY_DOT_ANIMATION_DELAY = 200; | + | * Compiler upgraded to Clang/bcc32c. [[bug>618]] |
| + | * Inactive sessions can be automatically reconnected. [[bug>2232]] | ||
| + | * Added dark theme support to: [[bug>1696]] | ||
| + | * 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 | ||
| + | * Using modern directory selection dialog that scales correctly and allows creating new directory. [[bug>2373]] [[bug>2389]] | ||
| + | * Optimized GUI when working with large subdirectory selection. [[bug>2396]] | ||
| + | * Change: Default to UTF-8 encoding in internal editor. [[bug>2397]] | ||
| + | * New DigiCert EV code signing certificate valid until March 2029 is used for signing binaries. | ||
| + | * TLS/SSL core upgraded to OpenSSL 3.5.5. | ||
| + | * WebDAV/HTTP core upgraded to neon 0.36.0. | ||
| + | * XML parser upgraded to Expat 2.7.4. | ||
| + | * Installer upgraded to Inno Setup 6.7.0 with dark mode support enabled. | ||
| + | * Increased WinSCP memory limit to 4 GB. [[bug>2412]] | ||
| + | * Defined and implemented interface for the .NET library. By @mjkent. [[bug>856]] | ||
| + | * Optimized TLS/SSL AES implementation. | ||
| + | * 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. | ||
| + | * Cryptography optimization. | ||
| + | * Support long AWS/S3 session tokens. [[bug>2403]] | ||
| + | * Prevent hang when new device is attached or removed while some mapped network drive is not available. [[bug>2382]] | ||
| + | * Copy and paste improvements: | ||
| + | * Consistently renaming local files dropped or pasted back to their source directory to avoid collisions. | ||
| + | * Bug fix: When copying local files to clipboard from system context menu, "cut" state of previously cut files was not cleared. | ||
| + | * Not redundantly verifying WebDAV or S3 certificate in Windows Certificate store if it is already marked as trusted in session settings. [[bug>2404]] | ||
| + | * Provide SNI when opening FTP data connection. [[bug>2410]] | ||
| + | * Optimized synchronization checklist sorting. | ||
| + | * Support for Beyond Compare 5 in Compare Files extension. [[bug>2417]] | ||
| + | * Convert unsupported SSH proxy to SSH tunnel when importing site from PuTTY. [[bug>2408]] | ||
| + | * FTP directory listing falls back to the other active/passive mode, consistently with file transfers. | ||
| + | * 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//. | ||
| + | * With INI file provided on command-line, using the same INI file when starting a new instance. | ||
| + | * Windows shell local file copy status window is centered on the main window. | ||
| + | * Made taskbar flashing configurable in GUI. [[bug>2411]] | ||
| + | * Control labels on transfer settings dialogs do not show keyboard accelerator cue, until ''Alt'' key is pressed. | ||
| + | * Not using drag images even with directory trees. [[bug>1274]] | ||
| + | * Allow configuring checksum commands. [[bug>2394]] | ||
| + | * Updated to JCL library 2.8.1. | ||
| + | * Updating jump list only when running with GUI. | ||
| + | * Made space on permissions box for longer translations. [[bug>2398]] | ||
| + | * Opening //Default Apps// //Settings// page directly to open it in the foreground and avoid flashing //Control Panel// window. | ||
| + | * Improving order in which Windows Narrator reads window controls. | ||
| + | * All edit boxes with history consistently do not auto complete and show 16 entries in the drop down. | ||
| + | * Removed obsolete //Preserve remote timestamp// session settings. | ||
| + | * 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]] | ||
| + | * Bug fix: List of network drives in drive drop down and directory tree did not always match. | ||
| + | * Bug fix: Host key prompt did not have the default button. | ||
| + | * Bug fix: When the local path specified on Open directory/Location profile dialog is not existing, when browsing for a new path, the trailing part of the nonexisting path was appended to the new path. | ||
| + | * Bug fix: Trying to enter an invalid link in local panel fails silently. | ||
| + | * Bug fix: After FTP data connection fails to open further use of the session is broken. | ||
| + | * Bug fix: Pasting cut files from the clipboard into a local panel copies them instead of moving them. [[bug>2400]] | ||
| + | * Bug fix: Some edits did not save their value to history when submitting with ''Enter''. | ||
| + | * Bug fix: Too long edit history dropdown can overflow monitor bounds. [[bug>2432]] | ||
| + | * Bug fix: Message box texts and some control labels are not visible to screen readers. [[bug>2413]] | ||
| + | * Bug fix: Failure when clicking tab close button while the session is already being closed. [[bug>2416]] | ||
| - | ····private SharingEngine sharingEngine; | + | ===== [[6.5.7]] 6.5.7 (not released yet) ((2026-06-17)) ===== |
| - | ····private SettingsHelper settingsHelper; | + | ··* Translations completed: Croatian, Finnish, Georgian, Italian and Serbian. |
| + | * TLS/SSL core upgraded to OpenSSL 3.3.7. | ||
| + | * Back-propagated fixes from 6.6.2 beta release: | ||
| + | * Bug fix: Failure setting ';'Session.DebugLogPath'' when running in impersonated context. [[bug>2441]] | ||
| + | * Security issue: fixed a remotely triggerable double-free in RSA key exchange. [[pbug>rsakex-double-free]] | ||
| + | * Minor security issue: fixed a remotely triggerable crash in NIST ECDSA signature verification. [[pbug>ecdsa-remotely-triggerable-assertion]] | ||
| + | * Bug fix: A specially crafted PKCS#7 or S/MIME signed message could trigger a use-after-free during PKCS#7 signature verification. CVE-2026-45447 fix from OpenSSL 3.4.6. | ||
| - | ····private String sessionId; | + | ===== [[6.5.6]] 6.5.6 ((2026-03-25)) ===== |
| - | ···private String password; | + | |
| - | ···private String adminName; | + | |
| - | ····private final static String ATTR_SESSION_ID = "sessionId"; | + | ··* Translations completed: Macedonian, and updated: Lithuanian, and Russian. |
| - | private final static String ATTR_PASSWORD = "password"; | + | ··* TLS/SSL core upgraded to OpenSSL 3.3.6. |
| - | private final static String ATTR_ADMIN_NAME = "adminName"; | + | * Back-propagated improvements from 6.6–6.6.1 beta release: |
| + | * New DigiCert EV code signing certificate valid until March 2029 is used for signing binaries. | ||
| + | ····* XML parser upgraded to Expat 2.7.5. | ||
| + | * Support for Beyond Compare 5 in Compare Files extension. [[bug>2417]] | ||
| + | * Bug fix: Checking if edited/opened file was modified externally didn't work for inactive sessions. [[bug>2426]] | ||
| - | ····private boolean needReconnect = false; | + | ===== [[6.5.5]] 6.5.5 ((2025-11-19)) ===== |
| - | ····private MediaProjectionManager projectionManager; | + | ··* Translation updated: Vietnamese. |
| + | * Bug fix: Pasting files using local directory tree context menu pastes them to the current directory, instead of the selected one. | ||
| + | * Bug fix: Failure when opening site imported from PuTTY with unsupported SSH proxy. [[bug>2407]] | ||
| + | * Bug fix: Incorrect hostname validation when connecting to S3 endpoint with certificate that does not cover root S3 hostname. [[bug>2409]] | ||
| - | ····private BroadcastReceiver mSharingServiceReceiver = new BroadcastReceiver() { | + | ===== [[6.5.4]] 6.5.4 ((2025-10-16)) ===== |
| - | @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)) { | + | ··* Translations updated: Belarusian and Georgian. |
| - | ···············notifySharingStop(); | + | * TLS/SSL core upgraded to OpenSSL 3.3.5. |
| - | ···············adminName = null; | + | ··* XML parser upgraded to Expat 2.7.3. |
| - | ···············updateUI(); | + | · * Added new ''ap-southeast-6'' AWS region. |
| - | ···············cancelSharingTimeout(); | + | ·* Bug fix: When restored after operation completed while minimized the window is disabled. [[bug>2393]] |
| - | ···············scheduleExitOnIdle(); | + | ·* Bug fix: Command ';'md5sums'' is incorrectly used to calculate MD5 checksum instead of ''md5sum''. [[bug>2392]] |
| + | ·* Bug fix: Incomplete FTP upload when the source stream/stdin reads less than requested. [[bug>2395]] | ||
| + | ·* Bug fix: ''Shift''-clicking //OK// button on Synchronization checklist window when synchronization in the background was not possible still closed the window. | ||
| + | ·* Bug fix: Failure after reloading file panel when number of files decreases. [[bug>2402]] | ||
| + | * Bug fix: Failure or silently missing headers when when S3 request headers were too long. | ||
| - | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_FAILED)) { | + | ===== [[6.5.3]] 6.5.3 ((2025-07-16)) ===== |
| - | 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)) { | + | ··* Translations updated: Belarusian, Brazilian Portuguese [[bug>2386]] and Slovenian. |
| - | ···············sharingEngine.setState(Const.STATE_DISCONNECTED); | + | ··* TLS/SSL core upgraded to OpenSSL 3.3.4. |
| - | ···············Toast.makeText(MainActivity.this, R.string.connection_failure_hint, Toast.LENGTH_LONG).show(); | + | ·* Temporarily not updating drag&drop shell extension for minor changes when installing for current user. [[bug>2381]] |
| - | ···············updateUI(); | + | ·* Bug fix: Failure when reading empty certificate authority configuration. |
| + | ··* Bug fix: Failure when running Maximized. [[bug>2387]] | ||
| + | ··* Bug fix: Incorrect scaling of navigation tree on Login and Preferences dialogs. [[bug>2385]] | ||
| + | ·* Bug fix: Incorrect scaling of drag&drop shell extension status display on Preferences dialog. | ||
| - | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED)) { | + | ===== [[6.5.2]] 6.5.2 ((2025-06-18)) ===== |
| - | ················startActivityForResult(projectionManager.createScreenCaptureIntent(), Const.REQUEST_SCREEN_SHARE); | + | |
| - | ···········} | + | |
| - | } | + | |
| - | ····}; | + | |
| - | ····@Override | + | ··* Translation updated: Brazilian Portuguese and Polish. |
| - | protected void onCreate(Bundle savedInstanceState) { | + | * Added new ''ap-east-2'' AWS region. |
| - | super.onCreate(savedInstanceState); | + | * Bug fix: Some translations (notably Japanese) are not loaded. [[bug>2372]] |
| - | setContentView(R.layout.activity_main); | + | * 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. | |
| - | if (savedInstanceState != null) { | + | * Bug fix: Failure when proxy hostname resolution fails with SFTP/SCP protocols. [[bug>2376]] |
| - | restoreInstanceState(savedInstanceState); | + | * 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]] | |
| - | Toolbar toolbar = findViewById(R.id.toolbar); | + | |
| - | setSupportActionBar(toolbar); | + | |
| - | settingsHelper = SettingsHelper.getInstance(this); | + | |
| - | sharingEngine = SharingEngineFactory.getSharingEngine(); | + | |
| - | sharingEngine.setEventListener(this); | + | |
| - | sharingEngine.setStateListener(this); | + | |
| - | + | ||
| - | DisplayMetrics metrics = new DisplayMetrics(); | + | |
| - | 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); | + | |
| - | sharingEngine.setScreenHeight(metrics.heightPixels); | + | |
| - | + | ||
| - | IntentFilter intentFilter = new IntentFilter(Const.ACTION_SCREEN_SHARING_START); | + | |
| - | intentFilter.addAction(Const.ACTION_SCREEN_SHARING_STOP); | + | |
| - | intentFilter.addAction(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED); | + | |
| - | intentFilter.addAction(Const.ACTION_SCREEN_SHARING_FAILED); | + | |
| - | intentFilter.addAction(Const.ACTION_CONNECTION_FAILURE); | + | |
| - | LocalBroadcastManager.getInstance(this).registerReceiver(mSharingServiceReceiver, intentFilter); | + | |
| - | + | ||
| - | projectionManager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE); | + | |
| - | + | ||
| - | initUI(); | + | |
| - | setDefaultSettings(); | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | protected void onResume() { | + | |
| - | super.onResume(); | + | |
| - | updateUI(); | + | |
| - | + | ||
| - | startService(new Intent(MainActivity.this, GestureDispatchService.class)); | + | |
| - | // connect(); | + | |
| - | checkAccessibility(); | + | |
| - | } | + | |
| - | + | ||
| - | private void checkAccessibility() { | + | |
| - | if (!Utils.isAccessibilityPermissionGranted(this)) { | + | |
| - | textViewConnect.setVisibility(View.INVISIBLE); | + | |
| - | new AlertDialog.Builder(this) | + | |
| - | .setMessage(R.string.accessibility_hint) | + | |
| - | .setPositiveButton(R.string.continue_button, new DialogInterface.OnClickListener() { | + | |
| - | @Override | + | |
| - | public void onClick(DialogInterface dialog, int which) { | + | |
| - | Intent intent = new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS); | + | |
| - | try { | + | |
| - | startActivityForResult(intent, 0); | + | |
| - | } catch (Exception e) { | + | |
| - | // Accessibility settings cannot be opened | + | |
| - | reportAccessibilityUnavailable(); | + | |
| - | } | + | |
| - | } | + | |
| - | }) | + | |
| - | .setCancelable(false) | + | |
| - | .create() | + | |
| - | .show(); | + | |
| - | } else { | + | |
| - | configureAndConnect(); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void reportAccessibilityUnavailable() { | + | |
| - | new AlertDialog.Builder(this) | + | |
| - | .setMessage(R.string.accessibility_unavailable_error) | + | |
| - | .setPositiveButton(R.string.button_exit, new DialogInterface.OnClickListener() { | + | |
| - | @Override | + | |
| - | public void onClick(DialogInterface dialog, int which) { | + | |
| - | exitApp(); | + | |
| - | } | + | |
| - | }) | + | |
| - | .setCancelable(false) | + | |
| - | .create() | + | |
| - | .show(); | + | |
| - | } | + | |
| - | + | ||
| - | private void configureAndConnect() { | + | |
| - | if (settingsHelper.getString(SettingsHelper.KEY_SERVER_URL) == null) { | + | |
| - | // Not configured yet | + | |
| - | settingsHelper.setString(SettingsHelper.KEY_SERVER_URL, BuildConfig.DEFAULT_SERVER_URL); | + | |
| - | settingsHelper.setString(SettingsHelper.KEY_SECRET, BuildConfig.DEFAULT_SECRET); | + | |
| - | settingsHelper.setBoolean(SettingsHelper.KEY_USE_DEFAULT, !BuildConfig.DEFAULT_SECRET.equals("")); | + | |
| - | Intent intent = new Intent(this, SettingsActivity.class); | + | |
| - | startActivityForResult(intent, Const.REQUEST_SETTINGS); | + | |
| - | return; | + | |
| - | } | + | |
| - | + | ||
| - | if (needReconnect) { | + | |
| - | // Here we go after changing settings | + | |
| - | needReconnect = false; | + | |
| - | if (sharingEngine.getState() != Const.STATE_DISCONNECTED) { | + | |
| - | sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> connect()); | + | |
| - | ············} else { | + | |
| - | connect(); | + | |
| - | } | + | |
| - | } else { | + | |
| - | if (sharingEngine.getState() == Const.STATE_DISCONNECTED && sharingEngine.getErrorReason() == null) { | + | |
| - | connect(); | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public void onDestroy() { | + | |
| - | try { | + | |
| - | LocalBroadcastManager.getInstance(this).unregisterReceiver(mSharingServiceReceiver); | + | |
| - | } catch (Exception e) { | + | |
| - | } | + | |
| - | super.onDestroy(); | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public void onSaveInstanceState(Bundle savedInstanceState) { | + | |
| - | savedInstanceState.putString(ATTR_SESSION_ID, sessionId); | + | |
| - | savedInstanceState.putString(ATTR_PASSWORD, password); | + | |
| - | savedInstanceState.putString(ATTR_ADMIN_NAME, adminName); | + | |
| - | super.onSaveInstanceState(savedInstanceState); | + | |
| - | } | + | |
| - | + | ||
| - | private void restoreInstanceState(Bundle savedInstanceState) { | + | |
| - | sessionId = savedInstanceState.getString(ATTR_SESSION_ID); | + | |
| - | password = savedInstanceState.getString(ATTR_PASSWORD); | + | |
| - | adminName = savedInstanceState.getString(ATTR_ADMIN_NAME); | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public void onBackPressed() { | + | |
| - | Toast.makeText(this, R.string.back_pressed, Toast.LENGTH_LONG).show(); | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public boolean onCreateOptionsMenu(Menu menu) { | + | |
| - | // Inflate the menu; this adds items to the action bar if it is present. | + | |
| - | getMenuInflater().inflate(R.menu.menu_main, menu); | + | |
| - | return true; | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public boolean onOptionsItemSelected(MenuItem item) { | + | |
| - | // Handle action bar item clicks here. The action bar will | + | |
| - | // automatically handle clicks on the Home/Up button, so long | + | |
| - | // as you specify a parent activity in AndroidManifest.xml. | + | |
| - | int id = item.getItemId(); | + | |
| - | + | ||
| - | if (id == R.id.action_settings) { | + | |
| - | if (adminName != null) { | + | |
| - | Toast.makeText(this, R.string.settings_unavailable, Toast.LENGTH_LONG).show(); | + | |
| - | return true; | + | |
| - | } | + | |
| - | Intent intent = new Intent(this, SettingsActivity.class); | + | |
| - | startActivityForResult(intent, Const.REQUEST_SETTINGS); | + | |
| - | cancelExitOnIdle(); | + | |
| - | return true; | + | |
| - | } else if (id == R.id.action_about) { | + | |
| - | showAbout(); | + | |
| - | return true; | + | |
| - | } | + | |
| - | + | ||
| - | return super.onOptionsItemSelected(item); | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public void onActivityResult(int requestCode, int resultCode, Intent data) { | + | |
| - | super.onActivityResult(requestCode, resultCode, data); | + | |
| - | + | ||
| - | if (requestCode == Const.REQUEST_SETTINGS) { | + | |
| - | if (resultCode == Const.RESULT_DIRTY) { | + | |
| - | needReconnect = true; | + | |
| - | } else { | + | |
| - | scheduleExitOnIdle(); | + | |
| - | } | + | |
| - | } else if (requestCode == Const.REQUEST_SCREEN_SHARE) { | + | |
| - | if (resultCode != RESULT_OK) { | + | |
| - | Toast.makeText(this, R.string.screen_cast_denied, Toast.LENGTH_LONG).show(); | + | |
| - | adminName = null; | + | |
| - | updateUI(); | + | |
| - | cancelSharingTimeout(); | + | |
| - | scheduleExitOnIdle(); | + | |
| - | } else { | + | |
| - | ScreenSharingHelper.startSharing(this, resultCode, data); | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void initUI() { | + | |
| - | imageViewConnStatus = findViewById(R.id.image_conn_status); | + | |
| - | textViewConnStatus = findViewById(R.id.conn_status); | + | |
| - | editTextSessionId = findViewById(R.id.session_id_edit); | + | |
| - | editTextPassword = findViewById(R.id.password_edit); | + | |
| - | textViewComment = findViewById(R.id.comment); | + | |
| - | textViewConnect = findViewById(R.id.reconnect); | + | |
| - | textViewSendLink = findViewById(R.id.send_link); | + | |
| - | textViewExit = findViewById(R.id.disconnect_exit); | + | |
| - | + | ||
| - | textViewConnect.setOnClickListener(v -> connect()); | + | |
| - | + | ||
| - | textViewSendLink.setOnClickListener(v -> sendLink()); | + | |
| - | + | ||
| - | textViewExit.setOnClickListener(v -> gracefulExit()); | + | |
| - | } | + | |
| - | + | ||
| - | private void gracefulExit() { | + | |
| - | if (adminName != null) { | + | |
| - | notifySharingStop(); | + | |
| - | ScreenSharingHelper.stopSharing(MainActivity.this, true); | + | |
| - | } | + | |
| - | sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> exitApp()); | + | |
| - | // 5 sec timeout to exit | + | |
| - | handler.postDelayed(() -> exitApp(), 5000); | + | |
| - | } | + | |
| - | + | ||
| - | ···private void exitApp() { | + | |
| - | Intent intent = new Intent(MainActivity.this, ScreenSharingService.class); | + | |
| - | stopService(intent); | + | |
| - | intent = new Intent(MainActivity.this, GestureDispatchService.class); | + | |
| - | stopService(intent); | + | |
| - | finishAffinity(); | + | |
| - | System.exit(0); | + | |
| - | } | + | |
| - | + | ||
| - | private void updateUI() { | + | |
| - | int[] stateLabels = {R.string.state_disconnected, R.string.state_connecting, R.string.state_connected, R.string.state_sharing, R.string.state_disconnecting}; | + | |
| - | int[] stateImages = {R.drawable.ic_disconnected, R.drawable.ic_connecting, R.drawable.ic_connected, R.drawable.ic_sharing, R.drawable.ic_connecting}; | + | |
| - | + | ||
| - | int state = sharingEngine.getState(); | + | |
| - | if (state == Const.STATE_CONNECTED && adminName != null) { | + | |
| - | imageViewConnStatus.setImageDrawable(getDrawable(stateImages[Const.STATE_SHARING])); | + | |
| - | textViewConnStatus.setText(stateLabels[Const.STATE_SHARING]); | + | |
| - | } else { | + | |
| - | imageViewConnStatus.setImageDrawable(getDrawable(stateImages[state])); | + | |
| - | ···········textViewConnStatus.setText(stateLabels[state]); | + | |
| - | } | + | |
| - | String serverUrl = Utils.prepareDisplayUrl(settingsHelper.getString(SettingsHelper.KEY_SERVER_URL)); | + | |
| - | + | ||
| - | textViewSendLink.setVisibility(state == Const.STATE_CONNECTED ? View.VISIBLE : View.INVISIBLE); | + | |
| - | textViewConnect.setVisibility(state == Const.STATE_DISCONNECTED ? View.VISIBLE : View.INVISIBLE); | + | |
| - | switch (state) { | + | |
| - | case Const.STATE_DISCONNECTED: | + | |
| - | editTextSessionId.setText(""); | + | |
| - | editTextPassword.setText(""); | + | |
| - | if (sharingEngine.getErrorReason() != null) { | + | |
| - | textViewComment.setText(getString(R.string.hint_connection_error, serverUrl)); | + | |
| - | } | + | |
| - | break; | + | |
| - | case Const.STATE_CONNECTING: | + | |
| - | textViewComment.setText(getString(R.string.hint_connecting, serverUrl)); | + | |
| - | break; | + | |
| - | case Const.STATE_DISCONNECTING: | + | |
| - | textViewComment.setText(getString(R.string.hint_disconnecting)); | + | |
| - | break; | + | |
| - | case Const.STATE_CONNECTED: | + | |
| - | editTextSessionId.setText(sessionId); | + | |
| - | editTextPassword.setText(password); | + | |
| - | textViewComment.setText(adminName != null ? | + | |
| - | getString(R.string.hint_sharing, adminName) : | + | |
| - | getString(R.string.hint_connected, serverUrl) | + | |
| - | ); | + | |
| - | break; | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void setDefaultSettings() { | + | |
| - | if (settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME) == null) { | + | |
| - | settingsHelper.setString(SettingsHelper.KEY_DEVICE_NAME, Build.MANUFACTURER + " " + Build.MODEL); | + | |
| - | } | + | |
| - | if (settingsHelper.getInt(SettingsHelper.KEY_BITRATE) == 0) { | + | |
| - | settingsHelper.setInt(SettingsHelper.KEY_BITRATE, Const.DEFAULT_BITRATE); | + | |
| - | } | + | |
| - | if (settingsHelper.getInt(SettingsHelper.KEY_FRAME_RATE) == 0) { | + | |
| - | settingsHelper.setInt(SettingsHelper.KEY_FRAME_RATE, Const.DEFAULT_FRAME_RATE); | + | |
| - | } | + | |
| - | if (settingsHelper.getInt(SettingsHelper.KEY_IDLE_TIMEOUT) == 0) { | + | |
| - | settingsHelper.setInt(SettingsHelper.KEY_IDLE_TIMEOUT, Const.DEFAULT_IDLE_TIMEOUT); | + | |
| - | } | + | |
| - | if (settingsHelper.getInt(SettingsHelper.KEY_PING_TIMEOUT) == 0) { | + | |
| - | settingsHelper.setInt(SettingsHelper.KEY_PING_TIMEOUT, Const.DEFAULT_PING_TIMEOUT); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void sendLink() { | + | |
| - | String url = settingsHelper.getString(SettingsHelper.KEY_SERVER_URL); | + | |
| - | url += "?session=" + sessionId + "&pin=" + password; | + | |
| - | try { | + | |
| - | Intent shareIntent = new Intent(Intent.ACTION_SEND); | + | |
| - | shareIntent.setType("text/plain"); | + | |
| - | shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.send_link_subject)); | + | |
| - | String shareMessage= getString(R.string.send_link_message, url, settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME)); | + | |
| - | shareIntent.putExtra(Intent.EXTRA_TEXT, shareMessage); | + | |
| - | startActivity(Intent.createChooser(shareIntent, getString(R.string.send_link_chooser))); | + | |
| - | } catch(Exception e) { | + | |
| - | e.printStackTrace(); | + | |
| - | Toast.makeText(this, R.string.send_link_failed, Toast.LENGTH_LONG).show(); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void showAbout() { | + | |
| - | ImageView imageView = new ImageView(this); | + | |
| - | imageView.setImageDrawable(getResources().getDrawable(R.mipmap.ic_launcher)); | + | |
| - | new AlertDialog.Builder(this) | + | |
| - | .setTitle(R.string.about_title) | + | |
| - | .setMessage(getString(R.string.about_message, BuildConfig.VERSION_NAME, BuildConfig.VARIANT)) | + | |
| - | .setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss()) | + | |
| - | .create() | + | |
| - | ···············.show(); | + | |
| - | } | + | |
| - | + | ||
| - | ···private void connect() { | + | |
| - | if (sessionId == null || password == null) { | + | |
| - | sessionId = Utils.randomString(8, true); | + | |
| - | password = Utils.randomString(4, true); | + | |
| - | } | + | |
| - | sharingEngine.setUsername(settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME)); | + | |
| - | sharingEngine.connect(this, sessionId, password, (success, errorReason) -> { | + | |
| - | if (!success) { | + | |
| - | if (errorReason != null && errorReason.equals(Const.ERROR_ICE_FAILED)) { | + | |
| - | errorReason = getString(R.string.connection_error_ice); | + | |
| - | } | + | |
| - | String message = getString(R.string.connection_error, settingsHelper.getString(SettingsHelper.KEY_SERVER_URL), errorReason); | + | |
| - | reportError(message); | + | |
| - | editTextSessionId.setText(null); | + | |
| - | editTextPassword.setText(null); | + | |
| - | } | + | |
| - | }); | + | |
| - | + | ||
| - | scheduleExitOnIdle(); | + | |
| - | } | + | |
| - | + | ||
| - | private void reportError(final String message) { | + | |
| - | // Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); | + | |
| - | final AlertDialog dialog = new AlertDialog.Builder(this) | + | |
| - | .setMessage(message) | + | |
| - | .setNegativeButton(R.string.copy_message, (dialog1, which) -> { | + | |
| - | ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); | + | |
| - | ClipData clip = ClipData.newPlainText(Const.LOG_TAG, message); | + | |
| - | clipboard.setPrimaryClip(clip); | + | |
| - | Toast.makeText(MainActivity.this, R.string.message_copied, Toast.LENGTH_LONG).show(); | + | |
| - | dialog1.dismiss(); | + | |
| - | }) | + | |
| - | .setPositiveButton(R.string.close, (dialog1, which) -> dialog1.dismiss()) | + | |
| - | .create(); | + | |
| - | try { | + | |
| - | dialog.show(); | + | |
| - | handler.postDelayed(() -> { | + | |
| - | try { | + | |
| - | dialog.dismiss(); | + | |
| - | } catch (Exception e) { | + | |
| - | } | + | |
| - | }, 10000); | + | |
| - | } catch (Exception e) { | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public void onStartSharing(String adminName) { | + | |
| - | // This event is raised when the admin joins the text room | + | |
| - | this.adminName = adminName; | + | |
| - | updateUI(); | + | |
| - | cancelExitOnIdle(); | + | |
| - | scheduleSharingTimeout(); | + | |
| - | ScreenSharingHelper.requestSharing(this); | + | |
| - | } | + | |
| - | + | ||
| - | ···@Override | + | |
| - | public void onStopSharing() { | + | |
| - | // This event is raised when the admin leaves the text room | + | |
| - | ········notifySharingStop(); | + | |
| - | adminName = null; | + | |
| - | updateUI(); | + | |
| - | cancelSharingTimeout(); | + | |
| - | scheduleExitOnIdle(); | + | |
| - | ScreenSharingHelper.stopSharing(this, false); | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public void onRemoteControlEvent(String event) { | + | |
| - | Intent intent = new Intent(MainActivity.this, GestureDispatchService.class); | + | |
| - | intent.setAction(Const.ACTION_GESTURE); | + | |
| - | intent.putExtra(Const.EXTRA_EVENT, event); | + | |
| - | startService(intent); | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public void onPing() { | + | |
| - | if (adminName != null) { | + | |
| - | cancelSharingTimeout(); | + | |
| - | scheduleSharingTimeout(); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | @Override | + | |
| - | public void onSharingApiStateChanged(int state) { | + | |
| - | updateUI(); | + | |
| - | if (state == Const.STATE_CONNECTED) { | + | |
| - | String rtpHost = Utils.getRtpUrl(settingsHelper.getString(SettingsHelper.KEY_SERVER_URL)); | + | |
| - | int rtpAudioPort = sharingEngine.getAudioPort(); | + | |
| - | int rtpVideoPort = sharingEngine.getVideoPort(); | + | |
| - | String testDstIp = settingsHelper.getString(SettingsHelper.KEY_TEST_DST_IP); | + | |
| - | if (testDstIp != null && !testDstIp.trim().equals("")) { | + | |
| - | rtpHost = testDstIp; | + | |
| - | rtpVideoPort = Const.TEST_RTP_PORT; | + | |
| - | Toast.makeText(this, "Test mode: sending stream to " + rtpHost + ":" + rtpVideoPort, Toast.LENGTH_LONG).show(); | + | |
| - | ············} | + | |
| - | + | ||
| - | ScreenSharingHelper.configure(this, settingsHelper.getBoolean(SettingsHelper.KEY_TRANSLATE_AUDIO), | + | |
| - | settingsHelper.getInt(SettingsHelper.KEY_FRAME_RATE), | + | |
| - | settingsHelper.getInt(SettingsHelper.KEY_BITRATE), | + | |
| - | rtpHost, | + | |
| - | rtpAudioPort, | + | |
| - | rtpVideoPort | + | |
| - | ); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void scheduleExitOnIdle() { | + | |
| - | int exitOnIdleTimeout = settingsHelper.getInt(SettingsHelper.KEY_IDLE_TIMEOUT); | + | |
| - | if (exitOnIdleTimeout > 0) { | + | |
| - | exitCounter = EXIT_PROMPT_SEC; | + | |
| - | handler.postDelayed(warningOnIdleRunnable, exitOnIdleTimeout * 1000); | + | |
| - | Log.d(Const.LOG_TAG, "Scheduling exit in " + (exitOnIdleTimeout * 1000) + " sec"); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void cancelExitOnIdle() { | + | |
| - | Log.d(Const.LOG_TAG, "Cancelling scheduled exit"); | + | |
| - | handler.removeCallbacks(warningOnIdleRunnable); | + | |
| - | handler.removeCallbacks(exitRunnable); | + | |
| - | } | + | |
| - | + | ||
| - | private Runnable exitRunnable = () -> { | + | |
| - | exitCounter--; | + | |
| - | if (exitCounter > 0) { | + | |
| - | TextView messageView = exitOnIdleDialog.findViewById(android.R.id.message); | + | |
| - | if (messageView != null) { | + | |
| - | messageView.setText(MainActivity.this.getResources().getString(R.string.app_idle_warning, exitCounter)); | + | |
| - | } | + | |
| - | scheduleExitRunnable(); | + | |
| - | + | ||
| - | } else { | + | |
| - | gracefulExit(); | + | |
| - | } | + | |
| - | }; | + | |
| - | + | ||
| - | private Runnable warningOnIdleRunnable = () -> { | + | |
| - | exitOnIdleDialog = new AlertDialog.Builder(MainActivity.this) | + | |
| - | ···············.setMessage(MainActivity.this.getResources().getString(R.string.app_idle_warning, exitCounter)) | + | |
| - | .setPositiveButton(R.string.button_exit, (dialog1, which) -> { | + | |
| - | ····················gracefulExit(); | + | |
| - | }) | + | |
| - | ···············.setNegativeButton(R.string.button_keep_idle, (dialog1, which) -> { | + | |
| - | scheduleExitOnIdle(); | + | |
| - | handler.removeCallbacks(exitRunnable); | + | |
| - | dialog1.dismiss(); | + | |
| - | }) | + | |
| - | .setCancelable(false) | + | |
| - | .create(); | + | |
| - | try { | + | |
| - | exitOnIdleDialog.show(); | + | |
| - | scheduleExitRunnable(); | + | |
| - | } catch (Exception e) { | + | |
| - | gracefulExit(); | + | |
| - | } | + | |
| - | }; | + | |
| - | + | ||
| - | private void scheduleExitRunnable() { | + | |
| - | handler.postDelayed(exitRunnable, 1000); | + | |
| - | } | + | |
| - | + | ||
| - | private void scheduleSharingTimeout() { | + | |
| - | int pingTimeout = settingsHelper.getInt(SettingsHelper.KEY_PING_TIMEOUT); | + | |
| - | if (pingTimeout > 0) { | + | |
| - | Log.d(Const.LOG_TAG, "Scheduling sharing stop in " + (pingTimeout * 1000) + " sec"); | + | |
| - | handler.postDelayed(sharingStopByPingTimeoutRunnable, pingTimeout * 1000); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void cancelSharingTimeout() { | + | |
| - | Log.d(Const.LOG_TAG, "Cancelling scheduled sharing stop"); | + | |
| - | handler.removeCallbacks(sharingStopByPingTimeoutRunnable); | + | |
| - | } | + | |
| - | + | ||
| - | private Runnable sharingStopByPingTimeoutRunnable = new Runnable() { | + | |
| - | @Override | + | |
| - | public void run() { | + | |
| - | Toast.makeText(MainActivity.this, R.string.app_sharing_session_ping_timeout, Toast.LENGTH_LONG).show(); | + | |
| - | if (adminName != null) { | + | |
| - | notifySharingStop(); | + | |
| - | ScreenSharingHelper.stopSharing(MainActivity.this, false); | + | |
| - | } | + | |
| - | adminName = null; | + | |
| - | updateUI(); | + | |
| - | cancelSharingTimeout(); | + | |
| - | scheduleExitOnIdle(); | + | |
| - | sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> connect()); | + | |
| - | } | + | |
| - | ···}; | + | |
| - | + | ||
| - | private Runnable overlayDotRunnable = new Runnable() { | + | |
| - | @Override | + | |
| - | public void run() { | + | |
| - | if (overlayDotDirection == 0) { | + | |
| - | return; | + | |
| - | } | + | |
| - | overlayDotAlpha += OVERLAY_DOT_ANIMATION_INCREMENT * overlayDotDirection; | + | |
| - | ···········if (overlayDotAlpha > 255) { | + | |
| - | overlayDotAlpha = 255; | + | |
| - | overlayDotDirection = -overlayDotDirection; | + | |
| - | } | + | |
| - | if (overlayDotAlpha < 128) { | + | |
| - | overlayDotAlpha = 128; | + | |
| - | overlayDotDirection = -overlayDotDirection; | + | |
| - | } | + | |
| - | overlayDot.setImageAlpha(overlayDotAlpha); | + | |
| - | ···········handler.postDelayed(overlayDotRunnable, OVERLAY_DOT_ANIMATION_DELAY); | + | |
| - | } | + | |
| - | }; | + | |
| - | + | ||
| - | private void notifySharingStart() { | + | |
| - | notifyGestureService(Const.ACTION_SCREEN_SHARING_START); | + | |
| - | if (settingsHelper.getBoolean(SettingsHelper.KEY_NOTIFY_SHARING)) { | + | |
| - | // Show a flashing dot | + | |
| - | Utils.lockDeviceRotation(this, true); | + | |
| - | overlayDot = createOverlayDot(); | + | |
| - | overlayDotAlpha = 0; | + | |
| - | overlayDotDirection = 1; | + | |
| - | handler.postDelayed(overlayDotRunnable, OVERLAY_DOT_ANIMATION_DELAY); | + | |
| - | + | ||
| - | } else { | + | |
| - | // Just show some dialog to trigger the traffic | + | |
| - | final AlertDialog dialog = new AlertDialog.Builder(MainActivity.this) | + | |
| - | .setMessage(R.string.share_start_text) | + | |
| - | .setPositiveButton(R.string.ok, (dialog1, which) -> dialog1.dismiss()) | + | |
| - | .create(); | + | |
| - | dialog.show(); | + | |
| - | handler.postDelayed(() -> { | + | |
| - | if (dialog != null && dialog.isShowing()) { | + | |
| - | try { | + | |
| - | dialog.dismiss(); | + | |
| - | } catch (Exception e) { | + | |
| - | } | + | |
| - | } | + | |
| - | }, 3000); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void notifySharingStop() { | + | |
| - | notifyGestureService(Const.ACTION_SCREEN_SHARING_STOP); | + | |
| - | if (settingsHelper.getBoolean(SettingsHelper.KEY_NOTIFY_SHARING)) { | + | |
| - | overlayDotDirection = 0; | + | |
| - | if (overlayDot != null) { | + | |
| - | WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); | + | |
| - | wm.removeView(overlayDot); | + | |
| - | overlayDot = null; | + | |
| - | } | + | |
| - | Utils.lockDeviceRotation(this, false); | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | private void notifyGestureService(String action) { | + | |
| - | Intent intent = new Intent(MainActivity.this, GestureDispatchService.class); | + | |
| - | intent.setAction(action); | + | |
| - | startService(intent); | + | |
| - | } | + | |
| - | + | ||
| - | public ImageView createOverlayDot() { | + | |
| - | int size = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_size); | + | |
| - | WindowManager.LayoutParams params = new WindowManager.LayoutParams(size, size, | + | |
| - | Utils.OverlayWindowType(), | + | |
| - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | + | |
| - | |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | + | |
| - | |WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, | + | |
| - | PixelFormat.TRANSLUCENT); | + | |
| - | params.gravity = Gravity.LEFT | Gravity.TOP; | + | |
| - | params.x = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_offset); | + | |
| - | params.y = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_offset); | + | |
| - | + | ||
| - | ImageView view = new ImageView(this); | + | |
| - | view.setImageResource(R.drawable.flash_dot); | + | |
| - | view.setImageAlpha(0); | + | |
| - | WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); | + | |
| - | wm.addView(view, params); | + | |
| - | return view; | + | |
| - | } | + | |
| - | } | + | |
| + | [[history_old|Older versions]] | ||
| + | ~~NOTOC~~ | ||
| + | ~~ARCHIVE=history_old~~ | ||