Differences
This shows you the differences between the selected revisions of the page.
| history 2022-09-12 | history 2026-06-23 11:38 (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-06-23)) ===== |
| - | 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: Czech, Dutch, Finnish, Polish and Simplified Chinese. |
| - | import androidx.appcompat.widget.Toolbar; | + | |
| - | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | + | |
| - | 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~~ | ||