Differences
This shows you the differences between the selected revisions of the page.
2022-09-12 | 2022-09-12 | ||
no summary (188.43.235.177) (hidden) (untrusted) | no summary (188.43.235.177) (hidden) (untrusted) | ||
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. | ||
+ | */ | ||
- | 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]]. | + | package com.hmdm.control; |
- | ====== | + | |
- | ······················· | + | import android.app.AlertDialog; |
+ | 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; |
+ | import androidx.appcompat.widget.Toolbar; | ||
+ | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | ||
- | ·· | + | import com.hmdm.control.janus.SharingEngineJanus; |
- | ·· | + | public class MainActivity extends AppCompatActivity implements SharingEngineJanus.EventListener, SharingEngineJanus.StateListener { |
- | ·· | + | private ImageView imageViewConnStatus; |
+ | private TextView textViewConnStatus; | ||
+ | private EditText editTextSessionId; | ||
+ | private EditText editTextPassword; | ||
+ | private TextView textViewComment; | ||
+ | private TextView textViewConnect; | ||
+ | private TextView textViewSendLink; | ||
+ | private TextView textViewExit; | ||
- | ====== ===== [[5.21]] 5.21 ((2022-06-15)) ===== | + | ····private ImageView overlayDot; |
+ | ····private Handler handler = new Handler(); | ||
+ | ···private int overlayDotAlpha; | ||
+ | ···private int overlayDotDirection = 1; | ||
- | ··* Bug fix: Hang when entering small directories with FTP protocol. [[bug>2087]] | + | ····private Dialog exitOnIdleDialog; |
- | ·* Bug fix: Lag when moving files from remote panel using drag&drop. [[bug>2088]] | + | ···private int exitCounter; |
- | ·* Bug fix: After certain operations, drive that was ever opened in local file panel cannot be safely removed temporarily. | + | ···private static final int EXIT_PROMPT_SEC = 10; |
- | ====== | + | |
- | ===== [[5.20.4]] 5.20.4 RC ((2022-06-08)) ===== | + | ····private static final int OVERLAY_DOT_ANIMATION_INCREMENT = 20; |
- | · | + | private static final int OVERLAY_DOT_ANIMATION_DELAY = 200; |
- | * SSH core and SSH private key tools (PuTTYgen and Pageant) upgraded to [[&url(puttychanges)|PuTTY 0.77]]. It brings the following changes: | + | |
- | * Pageant: Option ''%%--openssh-config%%'' to allow easy interoperation with Windows's ''ssh.exe''. [[pbug>win-pageant-openssh-interop]] | + | |
- | * Bug fix: PuTTYgen's mouse-based entropy collection now handles high-frequency mice without getting confused. [[pbug>win-puttygen-entropy-rate-limit]] | + | |
- | * Bug fix: Pageant can now handle large numbers of concurrent connections without hanging or crashing. [[pbug>win-pageant-max-connections-2]] | + | |
- | * Bug fix: If Pageant is started multiple times simultaneously, the instances should reliably agree on one of them to be the persistent server. [[pbug>win-pageant-concurrent-startup]] | + | |
- | * Improved error message when FTP server returns malformed response. [[bug>2077]] | + | |
- | * Translations updated: German and Slovak. | + | |
- | * Not listing remote directory when downloading file using FTP protocol with overwrite confirmations off. [[bug>2084]] | + | |
- | * Workaround for calls to system API failing when used first time against some network shares (Samba) with paths over the legacy Windows limit. [[bug>2082]] | + | |
- | * Workaround for an apparent bug in Windows 11 that prevents WinSCP from stopping Windows going to the sleep mode during transfers. [[bug>2083]] | + | |
- | * Workaround for specific encoding of commas in filenames (and particularly directory names) by OneDrive WebDAV interface. [[bug>2085]] | + | |
- | * Bug fix: When transferring a growing file, after its original size is reached, the ''Session.FileTransferProgress'' event starts being triggered continuously. [[bug>2078]] | + | |
- | * Bug fix: Restored pre-5.20.3 behaviour with MVS systems. [[bug>2086]] | + | |
- | ===== [[5.20.3]] 5.20.3 RC ((2022-05-17)) ===== | + | ····private SharingEngine sharingEngine; |
- | ··* Translations completed: Catalan, Czech, Dutch, Finnish, French, German, Hungarian, Italian, Japanese, Korean, Portuguese, Romanian, Russian, Simplified Chinese, Slovak, Spanish, Swedish, Traditional Chinese, Turkish and Ukrainian; and updated: Vietnamese | + | ····private SettingsHelper settingsHelper; |
- | * Streaming support in .NET assembly and scripting for FTP protocol. [[bug>1945]] | + | |
- | * Improved compatibility with MVS systems. [[bug>2069]] | + | |
- | * New .NET assembly method ''Session.TryGetFileInfo''. Thanks to @RachamimYaakobov. [[bug>2068]] | + | |
- | * Expanding environment variables in Open Directory/Location Profiles dialogs in local paths. [[bug>909]] | + | |
- | * Optimized loading of large directories. [[bug>1631]] | + | |
- | * TLS/SSL core upgraded to OpenSSL 1.1.1o. | + | |
- | * Installer upgraded to Inno Setup 6.2.1. | + | |
- | * When group (//Site/Shared//) to which new location profile is added is changed, switching the page to the target group. | + | |
- | * Preventing occasional exhaustion of resources while testing WinSCP executable version on repeated use of .NET assembly. [[bug>2075]] | + | |
- | * Bug fix: Capabilities of S3 sessions were not shown. | + | |
- | * Bug fix: Misplaced warning about unused scripting parameters when ''/rawsettings'' command-line switch is used. [[bug>2070]] | + | |
- | * Bug fix: File panels malfunction when files are dropped from other application to it. [[bug>2071]] | + | |
- | * Bug fix: When logging transfer statistics, recent transfer speed was logged instead of average speed of whole transfer. [[bug>2073]] | + | |
- | * Bug fix: z/OS PDS members without ISPF statistics are omitted in directory listing. [[bug>2076]] | + | |
- | ===== [[5.20.2]] 5.20.2 beta ((2022-04-06)) ===== | + | ····private String sessionId; |
+ | ···private String password; | ||
+ | ····private String adminName; | ||
- | ··* Translations updated: German and Vietnamese. | + | ····private final static String ATTR_SESSION_ID = "sessionId"; |
- | * SSH core upgraded to pre-release build of [[https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/|PuTTY 0.77]]. (*TODO replace with &url(puttychanges) once 0.77 is released*) \\ It brings the following changes: | + | private final static String ATTR_PASSWORD = "password"; |
- | * Support for HTTP Digest authentication for proxies. [[pbug>http-digestauth]] | + | ···private final static String ATTR_ADMIN_NAME = "adminName"; |
- | * Interactive username/password prompts for proxy authentication. [[bug>468]] [[pbug>proxy-password-prompt]] | + | |
- | * TLS/SSL core upgraded to OpenSSL 1.1.1n. | + | |
- | * WebDAV core upgraded to neon 0.32.2. | + | |
- | * XML parser upgraded to Expat 2.4.8. | + | |
- | * Allowed uploading edited files over encrypted session. [[bug>1989]] | + | |
- | * Building .NET assembly in Visual Studio 2019. | + | |
- | * Optionally not flashing taskbar button when WinSCP needs attention while in the background. [[bug>2067]] | + | |
- | * In .NET assembly or scripting with "nul" configuration, when WinSCP registry key does not exist, do not write statistics to INI file. [[bug>2066]] | + | |
- | ·* New less prominent icon for //Quit// command. | + | |
- | * Bug fix: Failure when attempting to switch to an unconnected sessions using keyboard shortcuts while another session is being connected. | + | |
- | * Bug fix: Removing all custom commands resets the default ones when INI file is used. [[bug>2059]] | + | |
- | * Bug fix: Failure when changing remote folder using directory tree while previous directory is still loading. [[bug>2060]] | + | |
- | * Bug fix: Drive that was ever opened in local file panel cannot be safely removed until WinSCP is closed. [[bug>2061]] | + | |
- | * Bug fix: //"Optimize connection buffer size"// checkbox was disabled for S3 although it has effect for the protocol. [[bug>2058]] | + | |
- | * Bug fix: Custom commands menu fail to open. [[bug>2062]] | + | |
- | * Bug fix: Local drive menu does not reflect some changes. [[bug>2063]] | + | |
- | * Bug fix: Using drive menu to change to an invalid drive fails silently. | + | |
- | * Bug fix: Scripting command ''ls'' in FTP session displays timestamps without year even if the server (notably IIS) provided the year, if it did not provide seconds. [[bug>2065]] | + | |
- | ===== [[5.20.1]] 5.20.1 beta ((2022-01-11)) ===== | + | ····private boolean needReconnect = false; |
- | · * File masks relative to the root of an operation. [[bug>2052]] | + | |
- | * Private key can be provided as string in .NET assembly and scripting. [[bug>2044]] | + | |
- | * Support for importing key files that are specified using home ''~'' prefix from OpenSSH ''config'' file. [[bug>2053]] | + | |
- | * Translation updated: German. | + | |
- | * TLS/SSL core upgraded to OpenSSL 1.1.1m. | + | |
- | * Commands that do not fit on //Custom Commands// toolbar are presented in drop down menu, instead of a horizontal bar. [[bug>554]] | + | |
- | * First hiding right-most //Custom Commands// toolbar commands that do not fit. | + | |
- | * Prevent hang when dragging files without drag&drop shell extensions when some mapped network drive is not available. [[bug>2054]] | + | |
- | * Added new ''ap-southeast-3'' AWS region. | + | |
- | * XML parser upgraded to Expat 2.4.2. | + | |
- | * Bug fix: When initial remote directory specified in the ''open'' command does not exist, the error was silently ignored. | + | |
- | * Bug fix: File color rules with path mask do not work for local files. | + | |
- | ===== [[5.20]] 5.20 beta ((2021-12-02)) ===== | + | ····private MediaProjectionManager projectionManager; |
- | ··* Support for ACL for S3 protocol. [[bug>1641]] | + | ····private BroadcastReceiver mSharingServiceReceiver = new BroadcastReceiver() { |
- | * SSH core upgraded to [[&url(puttychanges)|PuTTY 0.76]]. [[bug>1984]] \\ It brings the following changes: | + | ·······@Override |
- | ···* Support Curve448 key exchange method. [[pbug>curve448]] | + | ·······public void onReceive(Context context, Intent intent) { |
- | ···* Support Ed448 user and host keys. [[pbug>ed448]] | + | ···········if (intent == null || intent.getAction() == null) { |
- | * Support rsa-sha2-256 and rsa-sha2-512 SSH public key algorithms. [[bug>1952]] [[pbug>rsa-sha2]] | + | ···············return; |
- | * Change: SHA-256 fingerprints are not padded anymore. | + | ···········} |
- | * It is possible to import sessions from OpenSSH ''config'' file. [[bug>1896]] | + | ···········if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_START)) { |
- | * Change: Removed support for SSH-1. | + | ···············notifySharingStart(); |
- | * Optionally keeping symbolic link name in path instead of resolving it with SFTP protocol. [[bug>81]] | + | |
- | * Improved shortening long paths, when single level name is too long on its own. [[bug>2043]] | + | |
- | ·* WebDAV core upgraded to neon 0.32.1. | + | |
- | · * Allow displaying all VMS file revisions with FTP protocol. [[bug>1944]] | + | |
- | * Support reading S3 credentials from AWS CLI configuration. [[bug>1941]] | + | |
- | · * Bytes transferred are recorded in XML log and available in .NET assembly API. [[bug>597]] | + | |
- | * Generating PPK3 keys. | + | |
- | * It is possible to copy information to the clipboard from the Properties dialog. | + | |
- | * Made ''WinSCP.exe'' be deployed even when WinSCP NuGet package is included as transitive dependency. [[bug>2017]] | + | |
- | ·* When performing a single-item atomic operation, displaying indeterminate progress status. | + | |
- | ·* S3 //Security token// session option renamed to a more appropriate //Session Token//. | + | |
- | * Not resetting protocol to WebDAV when S3 protocol is selected and HTTP URL is pasted on the Login dialog. [[bug>2045]] | + | |
- | * Automatically reconnect when FTP server fails to open data connection, if it previously worked. [[bug>2046]] | + | |
- | * Confirm closing when multiple tabs are opened and auto workspace saving is not enabled, even when none of the tabs contain an active remote session. | + | |
- | ··* Including host key of tunnel session in generated code. [[bug>2006]] | + | |
- | * Support for custom certificate store files with configurable path. [[bug>2034]] | + | |
- | * Allowed providing tunnel private key passphrase in scripting. [[bug>2029]] | + | |
- | * Improved handling of long shell command error messages. [[bug>1949]] | + | |
- | * Considering global passive/active mode settings when importing sessions from FileZilla | + | |
- | * Automatically retry when S3 transfer fails with ''503'' code (//Slow down// or //Service unavailable//). [[bug>2047]] | + | |
- | * Improving formatting of errors displayed after an operation completed in //Continue on error// mode. | + | |
- | ··* ''Shift+Ctrl+R'' keyboard shortcut for //Reconnect Session// command. | + | |
- | * Change: Keyboard shortcut for //Restore Selection// command changed to ''Shift+Ctrl+S'' (''Shift+Ctrl+R'' previously). | + | |
- | * Change: Monitoring ''A:'' and ''B:'' drives. [[bug>2014]] | + | |
- | ·* Preventing beep when using ''Alt+Enter'' to open the Properties dialog. | + | |
- | * When collecting list of files for background transfer, say //"Listing"// instead of //"Calculating"// not to give an impression that it is a superfluous operation. [[bug>2026]] | + | |
- | * Checking for too many parameters with ''/keygen'' switch. | + | |
- | * PNG code upgraded to PngComponents 1.7.0. | + | |
- | * Warning when selecting too new key file. | + | |
- | * Warning when opening more than 100 tabs. [[bug>1997]] | + | |
- | * Including HTTP message into S3 error message. | + | |
- | * Bug fix: Do not say //"Terminated by user"// when the session has actually timed out. | + | |
- | * Bug fix: Cannot use passwords and passphrases longer than 255 characters in automation and various other purposes. [[bug>1988]] | + | |
- | * Bug fix: When size of a file downloaded with FTP protocol changes (or when ASCII mode is used) the logged size did not reflect the actual transfer size. | + | |
- | * Bug fix: When connecting disconnected session the directory might be loaded twice. | + | |
- | * Bug fix: Failure when entering directory that contains file with a slash in its name. [[bug>2016]] | + | |
- | * Bug fix: When S3 authentication region changes during session, previously visited buckets from the original authentication region could not be accessed anymore. [[bug>2027]] | + | |
- | * Bug fix: Failure when using multiple connections with local proxy command. [[bug>2028]] | + | |
- | * Bug fix: Configuration import did not remove old sites and did not load all GUI settings. [[bug>2040]] | + | |
- | * Bug fix: When S3 transfer fatally fails, error details were lost. | + | |
- | ===== [[5.19.6]] 5.19.6 (hotfix) ((2022-02-22)) ===== | + | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_STOP)) { |
+ | ················notifySharingStop(); | ||
+ | ···············adminName = null; | ||
+ | updateUI(); | ||
+ | ················cancelSharingTimeout(); | ||
+ | ···············scheduleExitOnIdle(); | ||
- | ··* Translation updated: German. | + | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_FAILED)) { |
- | · * Back-propagated fixes from 5.20–5.20.2 releases: | + | ···············String message = intent.getStringExtra(Const.EXTRA_MESSAGE); |
- | ···* TLS/SSL core upgraded to OpenSSL 1.1.1m. | + | ···············if (message != null) { |
- | ···* XML parser upgraded to Expat 2.4.6. | + | Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); |
+ | ···············} | ||
+ | adminName = null; | ||
+ | updateUI(); | ||
+ | ···············cancelSharingTimeout(); | ||
+ | scheduleExitOnIdle(); | ||
- | ===== [[5.19.5]] 5.19.5 ((2021-11-25)) ===== | + | ············} else if (intent.getAction().equals(Const.ACTION_CONNECTION_FAILURE)) { |
+ | sharingEngine.setState(Const.STATE_DISCONNECTED); | ||
+ | Toast.makeText(MainActivity.this, R.string.connection_failure_hint, Toast.LENGTH_LONG).show(); | ||
+ | ···············updateUI(); | ||
- | ··* Compatibility with Google Cloud S3 API when duplicating files. [[bug>2038]] | + | ············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED)) { |
- | * Compatibility with Google Cloud S3 API when deleting implicitly existing directories. [[bug>2042]] | + | ···············startActivityForResult(projectionManager.createScreenCaptureIntent(), Const.REQUEST_SCREEN_SHARE); |
- | · * Translation updated: Turkish. | + | ···········} |
- | ·* Logging a reference to [[bug>1952]] when an OpenSSH 8.8 (or newer) server refuses the key. | + | ·······} |
- | · * Bug fix: Crash when new contents is copied to the clipboard while downloading files pasted from the clipboard. [[bug>2036]] | + | ···}; |
- | ·* Bug fix: //Browse Remote Directory// command on Synchronization checklist did not locate file with spaces. | + | |
- | ·* Bug fix: Extension //ZIP and Upload// does not work with files in a drive root. [[bug>2039]] | + | |
- | * Bug fix: Path on Console window was not shortened when it did not fit. | + | |
- | ===== [[5.19.4]] 5.19.4 ((2021-10-24)) ===== | + | ····@Override |
+ | ····protected void onCreate(Bundle savedInstanceState) { | ||
+ | ········super.onCreate(savedInstanceState); | ||
+ | ········setContentView(R.layout.activity_main); | ||
- | ··* Translation updated: Hungarian. | + | ········if (savedInstanceState != null) { |
- | · * Showing release date on the About dialog. | + | ···········restoreInstanceState(savedInstanceState); |
- | ·* Support for custom certificate store files. [[bug>2034]] | + | ·······} |
- | * Allow other ''2xx'' responses to ''PWD'' command, not only the standard ''257''. [[bug>1768]] | + | |
- | * Bug fix: When there are both site folder and site with the same name and the site was selected when closing the Login dialog, when reopening, the folder was selected instead. | + | |
- | ===== [[5.19.3]] 5.19.3 ((2021-10-11)) ===== | + | ········Toolbar toolbar = findViewById(R.id.toolbar); |
+ | ·······setSupportActionBar(toolbar); | ||
+ | settingsHelper = SettingsHelper.getInstance(this); | ||
+ | ········sharingEngine = SharingEngineFactory.getSharingEngine(); | ||
+ | sharingEngine.setEventListener(this); | ||
+ | ·······sharingEngine.setStateListener(this); | ||
- | ··* Translation updated: French. | + | ········DisplayMetrics metrics = new DisplayMetrics(); |
- | * TLS/SSL core upgraded to OpenSSL 1.1.1l. | + | ScreenSharingHelper.getRealScreenSize(this, metrics); |
- | * Using //Documents// folder when the last used local directory in Explorer interface does not exist anymore. [[bug>2011]] | + | float videoScale = ScreenSharingHelper.adjustScreenMetrics(metrics); |
- | * Bug fix: TLS session resumption is not working for subsequent FTP transfers with TLS 1.3 when the server requires reuse of the session of the previous transfer. [[bug>2018]] | + | settingsHelper.setFloat(SettingsHelper.KEY_VIDEO_SCALE, videoScale); |
- | * Bug fix: Cannot access S3 bucket root when the access policy checks for empty prefix. [[bug>2021]] | + | ScreenSharingHelper.setScreenMetrics(this, metrics.widthPixels, metrics.heightPixels, metrics.densityDpi); |
- | * Bug fix: Response from ProFTPD FTP checksum commands is not recognized. [[bug>2023]] | + | |
- | * Bug fix: Failure when submitting prompt with //"Never ask me again"// selected. [[bug>2022]] | + | sharingEngine.setScreenWidth(metrics.widthPixels); |
- | * Bug fix: Panels are drawn incorrectly after toggling //Full row select//. [[bug>2025]] | + | sharingEngine.setScreenHeight(metrics.heightPixels); |
- | * Bug fix: Timeout while uploading files to some FTP servers using TLS 1.3. [[bug>2030]] | + | |
- | * Bug fix: Incomplete listing for S3 servers that indicate truncated listing after the contents and whose pagination is a multiple of 8 (e.g. Backblaze). [[bug>2032]] | + | 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~~ |