├── LICENSE ├── MetaCom ├── .gitignore ├── .idea │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── metarhia │ │ │ └── metacom │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ └── logback.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── metarhia │ │ │ │ └── metacom │ │ │ │ ├── activities │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainFragment.java │ │ │ │ ├── MetaComApplication.java │ │ │ │ ├── chat │ │ │ │ │ ├── ChatActivity.java │ │ │ │ │ ├── ChatFragment.java │ │ │ │ │ └── ChatLoginFragment.java │ │ │ │ ├── connection │ │ │ │ │ ├── ConnectionActivity.java │ │ │ │ │ └── ConnectionFragment.java │ │ │ │ └── files │ │ │ │ │ ├── DownloadFileDialog.java │ │ │ │ │ ├── FilesFragment.java │ │ │ │ │ └── UploadFileDialog.java │ │ │ │ ├── connection │ │ │ │ ├── AndroidJSTPConnection.java │ │ │ │ ├── Errors.java │ │ │ │ └── OkErrorHandler.java │ │ │ │ ├── interfaces │ │ │ │ ├── BackPressedHandler.java │ │ │ │ ├── ChatReconnectionListener.java │ │ │ │ ├── ConnectionCallback.java │ │ │ │ ├── DownloadFileByCodeListener.java │ │ │ │ ├── FileDownloadedListener.java │ │ │ │ ├── FileUploadedCallback.java │ │ │ │ ├── JoinRoomCallback.java │ │ │ │ ├── LeaveRoomCallback.java │ │ │ │ ├── MessageListener.java │ │ │ │ └── MessageSentCallback.java │ │ │ │ ├── models │ │ │ │ ├── ChatRoom.java │ │ │ │ ├── ChatRoomsManager.java │ │ │ │ ├── ConnectionInfoProvider.java │ │ │ │ ├── FilesManager.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageType.java │ │ │ │ ├── UserConnection.java │ │ │ │ └── UserConnectionsManager.java │ │ │ │ └── utils │ │ │ │ ├── Constants.java │ │ │ │ ├── FileUtils.java │ │ │ │ ├── HistoryCallback.java │ │ │ │ ├── KeyboardUtils.java │ │ │ │ ├── MainExecutor.java │ │ │ │ ├── NetworkUtils.java │ │ │ │ ├── PermissionUtils.java │ │ │ │ └── TextUtils.java │ │ └── res │ │ │ ├── drawable │ │ │ ├── ic_arrow_back_24dp.xml │ │ │ ├── ic_attach_file_24dp.xml │ │ │ ├── ic_attach_file_grey_24dp.xml │ │ │ ├── ic_error_24dp.xml │ │ │ ├── ic_file_download_24dp.xml │ │ │ ├── ic_file_download_grey_24dp.xml │ │ │ ├── ic_file_upload_24dp.xml │ │ │ ├── ic_file_upload_grey_24dp.xml │ │ │ ├── ic_insert_drive_file_black_24dp.xml │ │ │ ├── ic_mail_black_24dp.xml │ │ │ ├── message_in.xml │ │ │ ├── message_out.xml │ │ │ └── metarhia_logo.png │ │ │ ├── layout │ │ │ ├── activity_chat.xml │ │ │ ├── activity_connection.xml │ │ │ ├── activity_main.xml │ │ │ ├── bottom_notice.xml │ │ │ ├── fragment_chat.xml │ │ │ ├── fragment_chat_login.xml │ │ │ ├── fragment_connection.xml │ │ │ ├── fragment_download_code_dialog.xml │ │ │ ├── fragment_files.xml │ │ │ ├── fragment_main.xml │ │ │ ├── fragment_upload_file_dialog.xml │ │ │ ├── message_file.xml │ │ │ ├── message_in.xml │ │ │ ├── message_info.xml │ │ │ ├── message_out.xml │ │ │ └── toolbar.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── paths.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── metarhia │ │ └── metacom │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MetaCom/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | ### Android template 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the ART/Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | out/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | # Android Studio Navigation editor temp files 33 | .navigation/ 34 | 35 | # Android Studio captures folder 36 | captures/ 37 | 38 | # IntelliJ 39 | *.iml 40 | .idea/workspace.xml 41 | .idea/tasks.xml 42 | .idea/gradle.xml 43 | .idea/dictionaries 44 | .idea/libraries 45 | 46 | # Keystore files 47 | *.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | ### JetBrains template 60 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 61 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 62 | 63 | # User-specific stuff: 64 | .idea/**/workspace.xml 65 | .idea/**/tasks.xml 66 | 67 | # Sensitive or high-churn files: 68 | .idea/**/dataSources/ 69 | .idea/**/dataSources.ids 70 | .idea/**/dataSources.xml 71 | .idea/**/dataSources.local.xml 72 | .idea/**/sqlDataSources.xml 73 | .idea/**/dynamic.xml 74 | .idea/**/uiDesigner.xml 75 | 76 | # Gradle: 77 | .idea/**/gradle.xml 78 | .idea/**/libraries 79 | 80 | # CMake 81 | cmake-build-debug/ 82 | cmake-build-release/ 83 | 84 | # Mongo Explorer plugin: 85 | .idea/**/mongoSettings.xml 86 | 87 | ## File-based project format: 88 | *.iws 89 | 90 | ## Plugin-specific files: 91 | 92 | # IntelliJ 93 | 94 | # mpeltonen/sbt-idea plugin 95 | .idea_modules/ 96 | 97 | # JIRA plugin 98 | atlassian-ide-plugin.xml 99 | 100 | # Cursive Clojure plugin 101 | .idea/replstate.xml 102 | 103 | # Crashlytics plugin (for Android Studio and IntelliJ) 104 | com_crashlytics_export_strings.xml 105 | crashlytics.properties 106 | crashlytics-build.properties 107 | fabric.properties 108 | ### Gradle template 109 | .gradle 110 | /build/ 111 | 112 | # Ignore Gradle GUI config 113 | gradle-app.setting 114 | 115 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 116 | !gradle-wrapper.jar 117 | 118 | # Cache of project 119 | .gradletasknamecache 120 | 121 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 122 | # gradle/wrapper/gradle-wrapper.properties 123 | -------------------------------------------------------------------------------- /MetaCom/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /MetaCom/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /MetaCom/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MetaCom/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /MetaCom/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MetaCom/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /MetaCom/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MetaCom/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /MetaCom/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion '26.0.2' 6 | defaultConfig { 7 | applicationId "com.metarhia.metacom" 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | javaCompileOptions { 14 | annotationProcessorOptions { 15 | includeCompileClasspath true 16 | } 17 | } 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(include: ['*.jar'], dir: 'libs') 29 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 30 | exclude group: 'com.android.support', module: 'support-annotations' 31 | }) 32 | implementation 'com.android.support:appcompat-v7:25.3.1' 33 | 34 | implementation 'com.metarhia.jstp:jstp:0.8.6' 35 | compileOnly group: 'com.metarhia.jstp', name: 'jstp-compiler', version: '0.2.6' 36 | annotationProcessor group: 'com.metarhia.jstp', name: 'jstp-compiler', version: '0.2.6' 37 | 38 | implementation 'com.android.support.constraint:constraint-layout:1.0.1' 39 | implementation 'com.android.support:design:25.3.1' 40 | implementation 'com.jakewharton:butterknife:8.6.0' 41 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0' 42 | implementation 'com.android.support:support-v4:25.3.1' 43 | implementation 'com.android.support:appcompat-v7:25.3.1' 44 | implementation 'com.android.support:cardview-v7:25.3.1' 45 | implementation 'com.android.support:recyclerview-v7:25.3.1' 46 | testImplementation 'junit:junit:4.12' 47 | } 48 | -------------------------------------------------------------------------------- /MetaCom/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Lida/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /MetaCom/app/src/androidTest/java/com/metarhia/metacom/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.metarhia.metacom", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 43 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/assets/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} - logback: 5 | 6 | 7 | [%thread] jstp: %msg 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.metarhia.metacom.R; 7 | import com.metarhia.metacom.interfaces.BackPressedHandler; 8 | 9 | import static com.metarhia.metacom.activities.MainFragment.MAIN_FRAGMENT_TAG; 10 | 11 | /** 12 | * @author MariaKokshaikina 13 | */ 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | public static final String EXTRA_CONNECTION_ID = "extraConnectionId"; 17 | public static final String EXTRA_HOST_NAME = "extraHostName"; 18 | public static final String EXTRA_PORT = "extraPort"; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | int connectionID = getIntent().getIntExtra(EXTRA_CONNECTION_ID, -1); 26 | String hostName = getIntent().getStringExtra(EXTRA_HOST_NAME); 27 | int port = getIntent().getIntExtra(EXTRA_PORT, 0); 28 | 29 | if (savedInstanceState == null) { 30 | if (connectionID != -1) { 31 | getSupportFragmentManager().beginTransaction() 32 | .add(R.id.fragment_container, MainFragment.newInstance(connectionID, 33 | hostName, port), MAIN_FRAGMENT_TAG) 34 | .commit(); 35 | } 36 | } 37 | } 38 | 39 | @Override 40 | public void onBackPressed() { 41 | ((BackPressedHandler) getSupportFragmentManager().findFragmentByTag(MAIN_FRAGMENT_TAG)) 42 | .handleBackPress(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/MainFragment.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities; 2 | 3 | 4 | import android.content.DialogInterface; 5 | import android.content.SharedPreferences; 6 | import android.content.pm.PackageManager; 7 | import android.graphics.drawable.ColorDrawable; 8 | import android.os.Bundle; 9 | import android.preference.PreferenceManager; 10 | import android.support.annotation.NonNull; 11 | import android.support.design.widget.TabLayout; 12 | import android.support.v4.app.Fragment; 13 | import android.support.v4.app.FragmentPagerAdapter; 14 | import android.support.v4.view.PagerAdapter; 15 | import android.support.v4.view.ViewPager; 16 | import android.support.v7.app.AlertDialog; 17 | import android.view.LayoutInflater; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | 23 | import com.metarhia.metacom.R; 24 | import com.metarhia.metacom.activities.chat.ChatLoginFragment; 25 | import com.metarhia.metacom.activities.files.FilesFragment; 26 | import com.metarhia.metacom.interfaces.BackPressedHandler; 27 | import com.metarhia.metacom.models.UserConnectionsManager; 28 | import com.metarhia.metacom.utils.KeyboardUtils; 29 | import com.metarhia.metacom.utils.PermissionUtils; 30 | 31 | import java.util.ArrayList; 32 | 33 | import butterknife.BindView; 34 | import butterknife.ButterKnife; 35 | import butterknife.OnClick; 36 | import butterknife.Unbinder; 37 | 38 | 39 | /** 40 | * @author MariaKokshaikina 41 | */ 42 | public class MainFragment extends Fragment implements BackPressedHandler { 43 | 44 | public final static String MAIN_FRAGMENT_TAG = "MainFragmentTag"; 45 | private static final String KEY_CONNECTION_ID = "keyConnectionId"; 46 | private static final String KEY_HOST_NAME = "keyHostName"; 47 | private static final String KEY_PORT = "keyPort"; 48 | private static final String KEY_EXIT_DIALOG = "keyExitDialog"; 49 | private static final String KEY_PERMISSIONS_DIALOG = "keyPermissionsDialog"; 50 | 51 | @BindView(R.id.toolbar_title) 52 | TextView mToolbarTitle; 53 | @BindView(R.id.tab_layout) 54 | TabLayout mTabLayout; 55 | @BindView(R.id.view_pager) 56 | ViewPager mViewPager; 57 | private Unbinder mUnbinder; 58 | 59 | private ArrayList mFragmentArrayList; 60 | private ArrayList mFragmentTitles; 61 | private int mConnectionID; 62 | private boolean mExitDialog; 63 | private boolean mPermissionsDialog; 64 | private boolean isUIVisible = true; 65 | 66 | public static MainFragment newInstance(int connectionID, String hostName, int port) { 67 | Bundle args = new Bundle(); 68 | args.putInt(KEY_CONNECTION_ID, connectionID); 69 | args.putString(KEY_HOST_NAME, hostName); 70 | args.putInt(KEY_PORT, port); 71 | 72 | MainFragment fragment = new MainFragment(); 73 | fragment.setArguments(args); 74 | return fragment; 75 | } 76 | 77 | @Override 78 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 79 | Bundle savedInstanceState) { 80 | View view = inflater.inflate(R.layout.fragment_main, container, false); 81 | mUnbinder = ButterKnife.bind(this, view); 82 | 83 | if (getArguments() != null) { 84 | mConnectionID = getArguments().getInt(KEY_CONNECTION_ID); 85 | 86 | String host = getArguments().getString(KEY_HOST_NAME); 87 | String port = String.valueOf(getArguments().getInt(KEY_PORT)); 88 | 89 | String toolbarTitle = String.format(getString(R.string.title_pattern), host, port); 90 | setPages(); 91 | mToolbarTitle.setText(toolbarTitle); 92 | } 93 | 94 | if (savedInstanceState == null) { 95 | if (PermissionUtils.checkVersion() && 96 | !PermissionUtils.checkIfAlreadyHavePermission(getContext())) { 97 | showRequestDialog(); 98 | } 99 | } else { 100 | if (savedInstanceState.getBoolean(KEY_EXIT_DIALOG)) { 101 | handleBackPress(); 102 | } 103 | if (savedInstanceState.getBoolean(KEY_PERMISSIONS_DIALOG)) { 104 | showRequestDialog(); 105 | } 106 | } 107 | 108 | return view; 109 | } 110 | 111 | private void showRequestDialog() { 112 | mPermissionsDialog = true; 113 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 114 | builder.setTitle(R.string.permissions) 115 | .setMessage(R.string.permissions_info) 116 | .setCancelable(false) 117 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 118 | @Override 119 | public void onClick(DialogInterface dialogInterface, int i) { 120 | PermissionUtils.requestForStoragePermission(MainFragment.this); 121 | mPermissionsDialog = false; 122 | } 123 | }); 124 | AlertDialog alert = builder.create(); 125 | alert.show(); 126 | } 127 | 128 | @Override 129 | public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], 130 | @NonNull int[] grantResults) { 131 | switch (requestCode) { 132 | case PermissionUtils.REQUEST_CODE: { 133 | if (!(grantResults.length > 0 134 | && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { 135 | showForbidDialog(); 136 | } 137 | } 138 | } 139 | } 140 | 141 | private void showForbidDialog() { 142 | if (isUIVisible) { 143 | Toast.makeText(getContext(), getString(R.string.permissions_are_not_granted), 144 | Toast.LENGTH_SHORT).show(); 145 | } 146 | } 147 | 148 | @OnClick(R.id.toolbar_back) 149 | public void onBackClick() { 150 | handleBackPress(); 151 | } 152 | 153 | public void leaveServer() { 154 | UserConnectionsManager.get().removeConnection(UserConnectionsManager.get().getConnection 155 | (mConnectionID)); 156 | } 157 | 158 | private void setPages() { 159 | mFragmentArrayList = new ArrayList<>(); 160 | mFragmentArrayList.add(FilesFragment.newInstance(mConnectionID)); 161 | mFragmentArrayList.add(ChatLoginFragment.newInstance(mConnectionID)); 162 | 163 | PagerAdapter pagerAdapter = new FragmentPagerAdapter(getFragmentManager()) { 164 | 165 | @Override 166 | public int getCount() { 167 | return mFragmentArrayList.size(); 168 | } 169 | 170 | @Override 171 | public Fragment getItem(int position) { 172 | return mFragmentArrayList.get(position); 173 | } 174 | 175 | @Override 176 | public CharSequence getPageTitle(int position) { 177 | return mFragmentTitles.get(position); 178 | } 179 | }; 180 | 181 | mViewPager.setAdapter(pagerAdapter); 182 | 183 | mFragmentTitles = new ArrayList<>(); 184 | mFragmentTitles.add(getResources().getString(R.string.files)); 185 | mFragmentTitles.add(getResources().getString(R.string.chat)); 186 | 187 | mTabLayout.setupWithViewPager(mViewPager); 188 | 189 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 190 | @Override 191 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 192 | 193 | } 194 | 195 | @Override 196 | public void onPageSelected(int position) { 197 | KeyboardUtils.hideKeyboard(getActivity()); 198 | } 199 | 200 | @Override 201 | public void onPageScrollStateChanged(int state) { 202 | 203 | } 204 | }); 205 | } 206 | 207 | @Override 208 | public void onDestroyView() { 209 | super.onDestroyView(); 210 | mUnbinder.unbind(); 211 | } 212 | 213 | @Override 214 | public void handleBackPress() { 215 | mExitDialog = true; 216 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style 217 | .AlertDialogStyle); 218 | builder.setTitle(R.string.leave_server) 219 | .setMessage(R.string.leave_server_desc) 220 | .setCancelable(false) 221 | .setNegativeButton(R.string.cancel, 222 | new DialogInterface.OnClickListener() { 223 | public void onClick(DialogInterface dialog, int id) { 224 | mExitDialog = false; 225 | dialog.cancel(); 226 | } 227 | }) 228 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 229 | @Override 230 | public void onClick(DialogInterface dialogInterface, int i) { 231 | leaveServer(); 232 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences 233 | (getActivity().getApplicationContext()); 234 | SharedPreferences.Editor editor = sharedPref.edit(); 235 | editor.putBoolean(getString(R.string.shared_preferences_is_authorized), false); 236 | editor.apply(); 237 | getActivity().finish(); 238 | } 239 | }); 240 | AlertDialog alert = builder.create(); 241 | alert.show(); 242 | alert.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color 243 | .black14))); 244 | } 245 | 246 | @Override 247 | public void onSaveInstanceState(Bundle outState) { 248 | super.onSaveInstanceState(outState); 249 | outState.putBoolean(KEY_EXIT_DIALOG, mExitDialog); 250 | outState.putBoolean(KEY_PERMISSIONS_DIALOG, mPermissionsDialog); 251 | } 252 | 253 | @Override 254 | public void onPause() { 255 | super.onPause(); 256 | isUIVisible = false; 257 | } 258 | 259 | @Override 260 | public void onResume() { 261 | super.onResume(); 262 | isUIVisible = true; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/MetaComApplication.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities; 2 | 3 | import android.app.Application; 4 | 5 | import com.metarhia.metacom.connection.Errors; 6 | import com.metarhia.metacom.utils.Constants; 7 | 8 | /** 9 | * MetaCom application 10 | * 11 | * @author lidaamber 12 | */ 13 | 14 | public class MetaComApplication extends Application { 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | 20 | Errors.initResources(getResources()); 21 | Constants.initResources(getResources()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/chat/ChatActivity.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities.chat; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.metarhia.metacom.R; 7 | import com.metarhia.metacom.interfaces.BackPressedHandler; 8 | 9 | import static com.metarhia.metacom.activities.chat.ChatFragment.CHAT_FRAGMENT_TAG; 10 | 11 | /** 12 | * @author MariaKokshaikina 13 | */ 14 | public class ChatActivity extends AppCompatActivity { 15 | 16 | public static final String EXTRA_CONNECTION_ID = "extraConnectionId"; 17 | public static final String EXTRA_CHAT_ROOM_NAME = "extraChatRoomName"; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_chat); 23 | 24 | int connectionID = getIntent().getIntExtra(EXTRA_CONNECTION_ID, -1); 25 | String chatRoomName = getIntent().getStringExtra(EXTRA_CHAT_ROOM_NAME); 26 | 27 | if (savedInstanceState == null) { 28 | if (connectionID != -1) { 29 | getSupportFragmentManager().beginTransaction() 30 | .add(R.id.chat_container, ChatFragment.newInstance(connectionID, 31 | chatRoomName), CHAT_FRAGMENT_TAG) 32 | .commit(); 33 | } 34 | } 35 | } 36 | 37 | @Override 38 | public void onBackPressed() { 39 | ((BackPressedHandler) getSupportFragmentManager().findFragmentByTag(CHAT_FRAGMENT_TAG)) 40 | .handleBackPress(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/chat/ChatFragment.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities.chat; 2 | 3 | 4 | import android.app.Activity; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.graphics.drawable.ColorDrawable; 9 | import android.net.Uri; 10 | import android.os.Bundle; 11 | import android.os.Environment; 12 | import android.provider.MediaStore; 13 | import android.support.annotation.NonNull; 14 | import android.support.design.widget.TextInputEditText; 15 | import android.support.v4.app.Fragment; 16 | import android.support.v4.content.FileProvider; 17 | import android.support.v7.app.AlertDialog; 18 | import android.support.v7.widget.LinearLayoutManager; 19 | import android.support.v7.widget.RecyclerView; 20 | import android.view.ContextMenu; 21 | import android.view.LayoutInflater; 22 | import android.view.MenuItem; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.webkit.MimeTypeMap; 26 | import android.widget.ImageView; 27 | import android.widget.TextView; 28 | import android.widget.Toast; 29 | 30 | import com.metarhia.metacom.BuildConfig; 31 | import com.metarhia.metacom.R; 32 | import com.metarhia.metacom.interfaces.BackPressedHandler; 33 | import com.metarhia.metacom.interfaces.ChatReconnectionListener; 34 | import com.metarhia.metacom.interfaces.FileDownloadedListener; 35 | import com.metarhia.metacom.interfaces.FileUploadedCallback; 36 | import com.metarhia.metacom.interfaces.LeaveRoomCallback; 37 | import com.metarhia.metacom.interfaces.MessageListener; 38 | import com.metarhia.metacom.interfaces.MessageSentCallback; 39 | import com.metarhia.metacom.models.ChatRoom; 40 | import com.metarhia.metacom.models.ChatRoomsManager; 41 | import com.metarhia.metacom.models.Message; 42 | import com.metarhia.metacom.models.UserConnectionsManager; 43 | import com.metarhia.metacom.utils.Constants; 44 | import com.metarhia.metacom.utils.HistoryCallback; 45 | import com.metarhia.metacom.utils.PermissionUtils; 46 | 47 | import java.io.File; 48 | import java.io.FileNotFoundException; 49 | import java.io.InputStream; 50 | import java.util.ArrayList; 51 | import java.util.List; 52 | 53 | import butterknife.BindView; 54 | import butterknife.ButterKnife; 55 | import butterknife.OnClick; 56 | import butterknife.Unbinder; 57 | 58 | import static com.metarhia.metacom.models.MessageType.FILE; 59 | import static com.metarhia.metacom.models.MessageType.INFO; 60 | import static com.metarhia.metacom.models.MessageType.TEXT; 61 | import static com.metarhia.metacom.utils.TextUtils.copyToClipboard; 62 | 63 | /** 64 | * @author MariaKokshaikina 65 | */ 66 | public class ChatFragment extends Fragment implements MessageListener, MessageSentCallback, 67 | FileUploadedCallback, LeaveRoomCallback, FileDownloadedListener, BackPressedHandler, 68 | ChatReconnectionListener { 69 | 70 | public static final String CHAT_FRAGMENT_TAG = "ChatFragmentTag"; 71 | private static final String KEY_CONNECTION_ID = "keyConnectionId"; 72 | private static final String KEY_CHAT_ROOM_NAME = "keyChatRoomName"; 73 | private static final String KEY_MESSAGES_LIST = "keyMessagesList"; 74 | private static final String KEY_EXIT_DIALOG = "keyExitDialog"; 75 | 76 | private static final String TMP_METACOM_JPG = "/tmp-metacom.jpg"; 77 | private static final String AUTHORITY_STRING = BuildConfig.APPLICATION_ID + ".provider"; 78 | private static final int PICK_IMAGE_FROM_EXPLORER = 0; 79 | private static final int PICK_IMAGE_FROM_CAMERA = 1; 80 | private static final int TAKE_PHOTO = 2; 81 | private static final int FILE_EXPLORER = 3; 82 | 83 | @BindView(R.id.toolbar_title) 84 | TextView mToolbarTitle; 85 | @BindView(R.id.attach) 86 | ImageView mFileAttach; 87 | @BindView(R.id.messages_list) 88 | RecyclerView mMessagesView; 89 | @BindView(R.id.input_message) 90 | TextInputEditText mInputMessage; 91 | private Unbinder mUnbinder; 92 | 93 | private ArrayList mMessages; 94 | private MessagesAdapter mMessagesAdapter; 95 | private ChatRoom mChatRoom; 96 | private ChatRoomsManager mChatRoomsManager; 97 | private boolean isUIVisible = true; 98 | private boolean mExitDialog; 99 | 100 | public static ChatFragment newInstance(int connectionID, String chatRoomName) { 101 | Bundle args = new Bundle(); 102 | args.putInt(KEY_CONNECTION_ID, connectionID); 103 | args.putString(KEY_CHAT_ROOM_NAME, chatRoomName); 104 | 105 | ChatFragment fragment = new ChatFragment(); 106 | fragment.setArguments(args); 107 | return fragment; 108 | } 109 | 110 | @Override 111 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 112 | Bundle savedInstanceState) { 113 | View v = inflater.inflate(R.layout.fragment_chat, container, false); 114 | mUnbinder = ButterKnife.bind(this, v); 115 | 116 | registerForContextMenu(mFileAttach); 117 | 118 | if (getArguments() != null) { 119 | 120 | int connectionID = getArguments().getInt(KEY_CONNECTION_ID); 121 | String chatRoomName = getArguments().getString(KEY_CHAT_ROOM_NAME); 122 | 123 | mChatRoom = UserConnectionsManager.get().getConnection(connectionID) 124 | .getChatRoomsManager().getChatRoom(chatRoomName); 125 | mChatRoom.setMessageListener(this); 126 | mChatRoom.setChatReconnectionListener(this); 127 | 128 | mChatRoom.setFileDownloadedListener(this); 129 | mChatRoomsManager = UserConnectionsManager.get().getConnection(connectionID) 130 | .getChatRoomsManager(); 131 | 132 | mToolbarTitle.setText(chatRoomName); 133 | } 134 | 135 | LinearLayoutManager llm = new LinearLayoutManager(getContext()); 136 | mMessagesView.setLayoutManager(llm); 137 | 138 | if (savedInstanceState != null) { 139 | mMessages = (ArrayList) savedInstanceState.getSerializable(KEY_MESSAGES_LIST); 140 | if (savedInstanceState.getBoolean(KEY_EXIT_DIALOG)) { 141 | handleBackPress(); 142 | } 143 | } else { 144 | mMessages = new ArrayList<>(); 145 | String hasInterlocutorMessage = getString(mChatRoom.hasInterlocutor() 146 | ? R.string 147 | .has_interlocutor : R.string.no_interlocutor); 148 | mMessages.add(new Message(INFO, hasInterlocutorMessage, true)); 149 | } 150 | 151 | mMessagesAdapter = new MessagesAdapter(mMessages); 152 | mMessagesView.setAdapter(mMessagesAdapter); 153 | 154 | // still doesn't scroll properly for few last messages in recycleview 155 | mMessagesView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 156 | @Override 157 | public void onLayoutChange(View view, int left, int top, int right, final int bottom, 158 | int oldLeft, int oldTop, int oldRight, final int oldBottom) { 159 | if (bottom < oldBottom) { 160 | mMessagesView.smoothScrollBy(0, oldBottom - bottom); 161 | } else { 162 | int direction = oldBottom - bottom; 163 | if (mMessagesView.canScrollVertically(-direction)) { 164 | mMessagesView.smoothScrollBy(0, direction); 165 | } 166 | } 167 | } 168 | }); 169 | 170 | return v; 171 | } 172 | 173 | @Override 174 | public void onSaveInstanceState(Bundle outState) { 175 | super.onSaveInstanceState(outState); 176 | outState.putSerializable(KEY_MESSAGES_LIST, mMessages); 177 | outState.putBoolean(KEY_EXIT_DIALOG, mExitDialog); 178 | } 179 | 180 | @Override 181 | public void onMessageReceived(final Message message) { 182 | displayNewMessage(message); 183 | } 184 | 185 | private void displayNewMessage(Message message) { 186 | mMessages.add(message); 187 | if (isUIVisible) { 188 | updateMessagesView(); 189 | } 190 | } 191 | 192 | private void updateMessagesView() { 193 | mMessagesAdapter.notifyDataSetChanged(); 194 | mMessagesView.smoothScrollToPosition(mMessages.size()); 195 | } 196 | 197 | @Override 198 | public void onMessageSent(final Message message) { 199 | removeErrorIcon(message); 200 | } 201 | 202 | private void removeErrorIcon(Message message) { 203 | mMessages.get(mMessages.indexOf(message)).setWaiting(false); 204 | if (isUIVisible) { 205 | updateMessagesView(); 206 | } 207 | } 208 | 209 | @Override 210 | public void onMessageSentError(final String messageError) { 211 | // displayError(message); 212 | } 213 | 214 | private void displayError(String message) { 215 | if (isUIVisible) { 216 | Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); 217 | } 218 | } 219 | 220 | @OnClick(R.id.toolbar_back) 221 | public void onToolbarBackClick() { 222 | handleBackPress(); 223 | } 224 | 225 | public void leaveRoom() { 226 | mChatRoom.setMessageListener(null); 227 | mChatRoomsManager.leaveChatRoom(mChatRoom, this); 228 | } 229 | 230 | @OnClick(R.id.attach) 231 | public void onFileAttachClick() { 232 | if (PermissionUtils.checkIfAlreadyHavePermission(getContext())) { 233 | showFileChooser(); 234 | } else { 235 | if (PermissionUtils.checkVersion()) { 236 | PermissionUtils.requestForStoragePermission(this); 237 | } 238 | } 239 | } 240 | 241 | private void showForbidDialog() { 242 | if (isUIVisible) { 243 | Toast.makeText(getContext(), getString(R.string.permissions_are_not_granted), 244 | Toast.LENGTH_SHORT).show(); 245 | } 246 | } 247 | 248 | @OnClick(R.id.send) 249 | public void onSendMessageClick() { 250 | String messageText = mInputMessage.getText().toString(); 251 | if (!messageText.isEmpty()) { 252 | Message message = new Message(TEXT, messageText, false); 253 | message.setWaiting(true); 254 | 255 | mChatRoom.sendMessage(message, this); 256 | displayNewMessage(message); 257 | 258 | mInputMessage.setText(""); 259 | } 260 | } 261 | 262 | @Override 263 | public void onDestroyView() { 264 | super.onDestroyView(); 265 | mUnbinder.unbind(); 266 | } 267 | 268 | private void showFileChooser() { 269 | getActivity().openContextMenu(mFileAttach); 270 | } 271 | 272 | @Override 273 | public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo 274 | menuInfo) { 275 | if (v.getId() == mFileAttach.getId()) { 276 | menu.add(0, TAKE_PHOTO, 0, R.string.take_photo); 277 | menu.add(0, FILE_EXPLORER, 0, R.string.file_explorer); 278 | } 279 | } 280 | 281 | @Override 282 | public boolean onContextItemSelected(MenuItem item) { 283 | switch (item.getItemId()) { 284 | case TAKE_PHOTO: 285 | Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 286 | File f = new File(Environment.getExternalStorageDirectory() + TMP_METACOM_JPG); 287 | Uri uri = FileProvider.getUriForFile(getContext(), AUTHORITY_STRING, f); 288 | takePictureIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, uri); 289 | takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 290 | if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) { 291 | startActivityForResult(takePictureIntent, PICK_IMAGE_FROM_CAMERA); 292 | } 293 | return true; 294 | case FILE_EXPLORER: 295 | Intent intent = new Intent(); 296 | intent.setType("*/*"); 297 | intent.setAction(Intent.ACTION_GET_CONTENT); 298 | startActivityForResult(Intent.createChooser(intent, getString(R.string 299 | .select_file)), 300 | PICK_IMAGE_FROM_EXPLORER); 301 | return true; 302 | default: 303 | return super.onContextItemSelected(item); 304 | } 305 | } 306 | 307 | @Override 308 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 309 | super.onActivityResult(requestCode, resultCode, data); 310 | 311 | if ((requestCode == PICK_IMAGE_FROM_EXPLORER || requestCode == PICK_IMAGE_FROM_CAMERA) && 312 | resultCode == Activity.RESULT_OK) { 313 | Uri fileUri = null; 314 | switch (requestCode) { 315 | case PICK_IMAGE_FROM_EXPLORER: { 316 | fileUri = data.getData(); 317 | break; 318 | } 319 | case PICK_IMAGE_FROM_CAMERA: { 320 | File f = new File(Environment.getExternalStorageDirectory() 321 | + TMP_METACOM_JPG); 322 | fileUri = FileProvider.getUriForFile(getContext(), AUTHORITY_STRING, f); 323 | break; 324 | } 325 | } 326 | try { 327 | InputStream is = getActivity().getContentResolver().openInputStream(fileUri); 328 | String mimeType = getActivity().getContentResolver().getType(fileUri); 329 | mChatRoom.uploadFile(is, mimeType, this); 330 | } catch (FileNotFoundException e) { 331 | e.printStackTrace(); 332 | } 333 | } 334 | } 335 | 336 | @Override 337 | public void onFileUploaded(String fileCode) { 338 | final Message message = new Message(TEXT, 339 | getResources().getString(R.string.uploaded_file), false); 340 | onMessageReceived(message); 341 | } 342 | 343 | @Override 344 | public void onFileUploadError(final String message) { 345 | if (isUIVisible) { 346 | Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); 347 | } 348 | } 349 | 350 | @Override 351 | public void onLeavedRoom() { 352 | getActivity().finish(); 353 | } 354 | 355 | @Override 356 | public void onLeaveError(final String errorMessage) { 357 | if (isUIVisible) { 358 | Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show(); 359 | } 360 | } 361 | 362 | @Override 363 | public void onFileDownloaded(String path) { 364 | Message message = new Message(FILE, Constants.composeFilePathInfo(path), true); 365 | displayNewMessage(message); 366 | } 367 | 368 | @Override 369 | public void onFileDownloadError() { 370 | if (isUIVisible) { 371 | Toast.makeText(getContext(), R.string.err_download_failed, 372 | Toast.LENGTH_SHORT).show(); 373 | } 374 | } 375 | 376 | private void openFile(String filePath) { 377 | Uri uri = FileProvider.getUriForFile(getActivity(), 378 | BuildConfig.APPLICATION_ID + ".provider", 379 | new File(filePath)); 380 | 381 | String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); 382 | String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension 383 | (fileExtension); 384 | 385 | Intent intent = new Intent(Intent.ACTION_VIEW); 386 | intent.setDataAndType(uri, mimeType); 387 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 388 | startActivity(Intent.createChooser(intent, getString(R.string.open_file))); 389 | } 390 | 391 | @Override 392 | public void onPause() { 393 | super.onPause(); 394 | isUIVisible = false; 395 | } 396 | 397 | @Override 398 | public void onResume() { 399 | super.onResume(); 400 | isUIVisible = true; 401 | updateMessagesView(); 402 | } 403 | 404 | @Override 405 | public void handleBackPress() { 406 | mExitDialog = true; 407 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style 408 | .AlertDialogStyle); 409 | builder.setTitle(R.string.leave_chat) 410 | .setMessage(R.string.leave_chat_desc) 411 | .setCancelable(false) 412 | .setNegativeButton(R.string.cancel, 413 | new DialogInterface.OnClickListener() { 414 | public void onClick(DialogInterface dialog, int id) { 415 | mExitDialog = false; 416 | dialog.cancel(); 417 | } 418 | }) 419 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 420 | @Override 421 | public void onClick(DialogInterface dialogInterface, int i) { 422 | showSaveHistoryDialog(); 423 | } 424 | }); 425 | AlertDialog alert = builder.create(); 426 | alert.show(); 427 | alert.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color 428 | .black14))); 429 | } 430 | 431 | private void showSaveHistoryDialog() { 432 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style 433 | .AlertDialogStyle); 434 | builder.setTitle(R.string.save_history) 435 | .setMessage(R.string.save_history_desc) 436 | .setCancelable(false) 437 | .setNegativeButton(R.string.no, 438 | new DialogInterface.OnClickListener() { 439 | public void onClick(DialogInterface dialog, int id) { 440 | leaveRoom(); 441 | } 442 | }) 443 | .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { 444 | @Override 445 | public void onClick(DialogInterface dialogInterface, int i) { 446 | saveHistory(); 447 | leaveRoom(); 448 | } 449 | }); 450 | AlertDialog alert = builder.create(); 451 | alert.show(); 452 | alert.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color 453 | .black14))); 454 | } 455 | 456 | private void saveHistory() { 457 | mChatRoomsManager.saveHistory(mMessages, new HistoryCallback() { 458 | @Override 459 | public void onHistorySaved(String filename) { 460 | String message = String.format(getString(R.string.saved_history), filename); 461 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT) 462 | .show(); 463 | } 464 | 465 | @Override 466 | public void onSaveError() { 467 | Toast.makeText(getActivity(), 468 | getString(R.string.save_history_error), Toast.LENGTH_SHORT) 469 | .show(); 470 | } 471 | }); 472 | } 473 | 474 | @Override 475 | public void onConnectionLost() { 476 | if (isUIVisible) { 477 | Toast.makeText(getContext(), getString(R.string.connection_lost), Toast.LENGTH_SHORT) 478 | .show(); 479 | } 480 | } 481 | 482 | @Override 483 | public void onRejoinSuccess(boolean hasInterlocutor) { 484 | if (isUIVisible) { 485 | Toast.makeText(getContext(), getString(R.string.connection_established), Toast 486 | .LENGTH_SHORT).show(); 487 | } 488 | } 489 | 490 | @Override 491 | public void onRejoinError(String errorMessage) { 492 | if (isUIVisible) { 493 | Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show(); 494 | } 495 | } 496 | 497 | @Override 498 | public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], 499 | @NonNull int[] grantResults) { 500 | switch (requestCode) { 501 | case PermissionUtils.REQUEST_CODE: { 502 | if (grantResults.length > 0 503 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 504 | showFileChooser(); 505 | } else { 506 | showForbidDialog(); 507 | } 508 | } 509 | } 510 | } 511 | 512 | class MessagesAdapter extends RecyclerView.Adapter { 513 | 514 | private static final int TYPE_INFO = 0; 515 | private static final int TYPE_INCOMING_FILE = 1; 516 | private static final int TYPE_TEXT_IN = 2; 517 | private static final int TYPE_TEXT_OUT = 3; 518 | 519 | private List messages; 520 | 521 | MessagesAdapter(List messages) { 522 | this.messages = messages; 523 | } 524 | 525 | @Override 526 | public int getItemViewType(int position) { 527 | Message message = messages.get(position); 528 | if (message.getType() == INFO) return TYPE_INFO; 529 | if (message.getType() == FILE && message.isIncoming()) return TYPE_INCOMING_FILE; 530 | if (message.getType() == TEXT) 531 | return message.isIncoming() ? TYPE_TEXT_IN : TYPE_TEXT_OUT; 532 | return -1; 533 | } 534 | 535 | @Override 536 | public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 537 | int resource = -1; 538 | if (viewType == TYPE_INFO) { 539 | resource = R.layout.message_info; 540 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false); 541 | return new InfoMessageViewHolder(v); 542 | } 543 | if (viewType == TYPE_INCOMING_FILE) { 544 | resource = R.layout.message_file; 545 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false); 546 | return new FileMessageViewHolder(v); 547 | } 548 | if (viewType == TYPE_TEXT_IN) resource = R.layout.message_in; 549 | if (viewType == TYPE_TEXT_OUT) resource = R.layout.message_out; 550 | View v = LayoutInflater.from(parent.getContext()).inflate(resource, parent, false); 551 | return new TextMessageViewHolder(v); 552 | } 553 | 554 | @Override 555 | public void onBindViewHolder(MessageViewHolder holder, int position) { 556 | final Message message = messages.get(position); 557 | final String messageContent = message.getContent(); 558 | if (holder instanceof TextMessageViewHolder) { 559 | TextMessageViewHolder textMessageViewHolder = (TextMessageViewHolder) holder; 560 | textMessageViewHolder.messageText.setText(messageContent); 561 | textMessageViewHolder.errorIcon.setVisibility(message.isWaiting() ? View 562 | .VISIBLE : View.GONE); 563 | registerForContextMenu(textMessageViewHolder.messageLayout); 564 | textMessageViewHolder.messageLayout.setOnCreateContextMenuListener(new View 565 | .OnCreateContextMenuListener() { 566 | @Override 567 | public void onCreateContextMenu(ContextMenu contextMenu, View view, 568 | ContextMenu.ContextMenuInfo contextMenuInfo) { 569 | contextMenu.add(getString(R.string.copy)).setOnMenuItemClickListener 570 | (new MenuItem.OnMenuItemClickListener() { 571 | @Override 572 | public boolean onMenuItemClick(MenuItem menuItem) { 573 | copyToClipboard(getActivity(), messageContent); 574 | return false; 575 | } 576 | }); 577 | if (message.isWaiting()) { 578 | contextMenu.add(getString(R.string.resend)) 579 | .setOnMenuItemClickListener(new MenuItem 580 | .OnMenuItemClickListener() { 581 | @Override 582 | public boolean onMenuItemClick(MenuItem menuItem) { 583 | mMessages.remove(message); 584 | mChatRoom.sendMessage(message, ChatFragment.this); 585 | displayNewMessage(message); 586 | return false; 587 | } 588 | }); 589 | } 590 | } 591 | }); 592 | } 593 | if (holder instanceof FileMessageViewHolder) { 594 | FileMessageViewHolder fileMessageViewHolder = (FileMessageViewHolder) holder; 595 | fileMessageViewHolder.fileImageView.setOnClickListener(new View.OnClickListener() { 596 | @Override 597 | public void onClick(View view) { 598 | String path = messageContent.substring(messageContent.indexOf('/')); 599 | ChatFragment.this.openFile(path); 600 | } 601 | }); 602 | } 603 | if (holder instanceof InfoMessageViewHolder) { 604 | InfoMessageViewHolder infoMessageViewHolder = (InfoMessageViewHolder) holder; 605 | infoMessageViewHolder.messageText.setText(messageContent); 606 | } 607 | } 608 | 609 | @Override 610 | public int getItemCount() { 611 | return messages.size(); 612 | } 613 | 614 | class MessageViewHolder extends RecyclerView.ViewHolder { 615 | 616 | MessageViewHolder(View itemView) { 617 | super(itemView); 618 | } 619 | } 620 | 621 | class TextMessageViewHolder extends MessageViewHolder { 622 | 623 | private TextView messageText; 624 | private View messageLayout; 625 | private ImageView errorIcon; 626 | 627 | TextMessageViewHolder(View itemView) { 628 | super(itemView); 629 | messageText = ButterKnife.findById(itemView, R.id.message_text); 630 | messageLayout = ButterKnife.findById(itemView, R.id.message_layout); 631 | errorIcon = ButterKnife.findById(itemView, R.id.send_error); 632 | } 633 | 634 | } 635 | 636 | class FileMessageViewHolder extends MessageViewHolder { 637 | 638 | private ImageView fileImageView; 639 | 640 | private FileMessageViewHolder(View itemView) { 641 | super(itemView); 642 | fileImageView = ButterKnife.findById(itemView, R.id.file); 643 | } 644 | } 645 | 646 | class InfoMessageViewHolder extends MessageViewHolder { 647 | 648 | private TextView messageText; 649 | 650 | InfoMessageViewHolder(View itemView) { 651 | super(itemView); 652 | messageText = ButterKnife.findById(itemView, R.id.message_text); 653 | } 654 | } 655 | } 656 | } 657 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/chat/ChatLoginFragment.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities.chat; 2 | 3 | 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.design.widget.TextInputEditText; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v7.widget.AppCompatButton; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.ProgressBar; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | 16 | import com.metarhia.metacom.R; 17 | import com.metarhia.metacom.interfaces.JoinRoomCallback; 18 | import com.metarhia.metacom.models.ChatRoomsManager; 19 | import com.metarhia.metacom.models.UserConnectionsManager; 20 | 21 | import butterknife.BindView; 22 | import butterknife.ButterKnife; 23 | import butterknife.OnClick; 24 | import butterknife.Unbinder; 25 | 26 | 27 | /** 28 | * @author MariaKokshaikina 29 | */ 30 | public class ChatLoginFragment extends Fragment implements JoinRoomCallback { 31 | 32 | private static final String KEY_CONNECTION_ID = "keyConnectionId"; 33 | 34 | @BindView(R.id.chat_name) 35 | TextInputEditText mChatNameEditText; 36 | @BindView(R.id.submit) 37 | TextView mButtonSubmit; 38 | @BindView(R.id.spinner) 39 | ProgressBar mSpinner; 40 | private Unbinder mUnbinder; 41 | 42 | private ChatRoomsManager mManager; 43 | private int mID; 44 | private boolean isUIVisible = true; 45 | 46 | public static ChatLoginFragment newInstance(int connectionID) { 47 | Bundle args = new Bundle(); 48 | args.putInt(KEY_CONNECTION_ID, connectionID); 49 | ChatLoginFragment fragment = new ChatLoginFragment(); 50 | fragment.setArguments(args); 51 | return fragment; 52 | } 53 | 54 | @Override 55 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 56 | Bundle savedInstanceState) { 57 | View view = inflater.inflate(R.layout.fragment_chat_login, container, false); 58 | mUnbinder = ButterKnife.bind(this, view); 59 | 60 | setRetainInstance(true); 61 | 62 | if (getArguments() != null && getArguments().containsKey(KEY_CONNECTION_ID)) { 63 | mID = getArguments().getInt(KEY_CONNECTION_ID); 64 | mManager = UserConnectionsManager.get().getConnection(mID).getChatRoomsManager(); 65 | } 66 | 67 | return view; 68 | } 69 | 70 | @OnClick(R.id.submit) 71 | public void onButtonSubmitClick() { 72 | String chatName = mChatNameEditText.getText().toString(); 73 | if (!chatName.isEmpty()) { 74 | mButtonSubmit.setVisibility(View.GONE); 75 | mSpinner.setVisibility(View.VISIBLE); 76 | mChatNameEditText.setEnabled(false); 77 | mManager.addChatRoom(chatName, this); 78 | } 79 | } 80 | 81 | @Override 82 | public void onJoinedRoom(boolean hasInterlocutor) { 83 | mButtonSubmit.setVisibility(View.VISIBLE); 84 | mSpinner.setVisibility(View.INVISIBLE); 85 | mChatNameEditText.setEnabled(true); 86 | Intent intent = new Intent(getActivity(), ChatActivity.class); 87 | intent.putExtra(ChatActivity.EXTRA_CONNECTION_ID, mID); 88 | intent.putExtra(ChatActivity.EXTRA_CHAT_ROOM_NAME, mChatNameEditText.getText().toString()); 89 | startActivity(intent); 90 | } 91 | 92 | @Override 93 | public void onJoinError(final String errorMessage) { 94 | mButtonSubmit.setVisibility(View.VISIBLE); 95 | mSpinner.setVisibility(View.INVISIBLE); 96 | mChatNameEditText.setEnabled(true); 97 | if (isUIVisible) { 98 | Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show(); 99 | } 100 | } 101 | 102 | @Override 103 | public void onDestroyView() { 104 | super.onDestroyView(); 105 | mUnbinder.unbind(); 106 | } 107 | 108 | @Override 109 | public void onResume() { 110 | super.onResume(); 111 | isUIVisible = true; 112 | } 113 | 114 | @Override 115 | public void onPause() { 116 | super.onPause(); 117 | isUIVisible = false; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/connection/ConnectionActivity.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities.connection; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.metarhia.metacom.R; 7 | 8 | /** 9 | * @author MariaKokshaikina 10 | */ 11 | public class ConnectionActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_connection); 17 | 18 | if (savedInstanceState == null) { 19 | getSupportFragmentManager().beginTransaction() 20 | .add(R.id.fragment_container, new ConnectionFragment()) 21 | .commit(); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/connection/ConnectionFragment.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities.connection; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.preference.PreferenceManager; 10 | import android.support.design.widget.TextInputEditText; 11 | import android.support.v4.app.Fragment; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.AdapterView; 16 | import android.widget.ArrayAdapter; 17 | import android.widget.AutoCompleteTextView; 18 | import android.widget.ProgressBar; 19 | import android.widget.TextView; 20 | import android.widget.Toast; 21 | 22 | import com.metarhia.metacom.R; 23 | import com.metarhia.metacom.activities.MainActivity; 24 | import com.metarhia.metacom.interfaces.ConnectionCallback; 25 | import com.metarhia.metacom.models.ConnectionInfoProvider; 26 | import com.metarhia.metacom.models.UserConnectionsManager; 27 | 28 | import java.util.Map; 29 | 30 | import butterknife.BindView; 31 | import butterknife.ButterKnife; 32 | import butterknife.OnClick; 33 | import butterknife.Unbinder; 34 | 35 | /** 36 | * @author MariaKokshaikina 37 | */ 38 | public class ConnectionFragment extends Fragment implements ConnectionCallback { 39 | 40 | @BindView(R.id.host) 41 | AutoCompleteTextView mHostEditText; 42 | @BindView(R.id.port) 43 | TextInputEditText mPortEditText; 44 | @BindView(R.id.submit) 45 | TextView mButtonSubmit; 46 | @BindView(R.id.spinner) 47 | ProgressBar mSpinner; 48 | private Unbinder mUnbinder; 49 | 50 | private String mHost; 51 | private Integer mPort; 52 | private boolean mIsUIVisible = true; 53 | 54 | @Override 55 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 56 | Bundle savedInstanceState) { 57 | View v = inflater.inflate(R.layout.fragment_connection, container, false); 58 | mUnbinder = ButterKnife.bind(this, v); 59 | 60 | final Map infoList = ConnectionInfoProvider.restoreConnectionInfo 61 | (getActivity()); 62 | if (infoList != null) { 63 | String[] hosts = infoList.keySet().toArray(new String[infoList.size()]); 64 | final ArrayAdapter adapter = new ArrayAdapter<>(getContext(), R.layout 65 | .support_simple_spinner_dropdown_item, hosts); 66 | mHostEditText.setAdapter(adapter); 67 | mHostEditText.setOnItemClickListener(new AdapterView.OnItemClickListener() { 68 | @Override 69 | public void onItemClick(AdapterView adapterView, View view, int i, long l) { 70 | String host = adapter.getItem(i); 71 | mPortEditText.setText(infoList.get(host) + ""); 72 | } 73 | }); 74 | } 75 | 76 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity 77 | ().getApplicationContext()); 78 | String host = sharedPref.getString(getString(R.string.shared_preferences_host), null); 79 | int port = sharedPref.getInt(getString(R.string.shared_preferences_port), -1); 80 | boolean isAuthorized = sharedPref.getBoolean(getString(R.string 81 | .shared_preferences_is_authorized), false); 82 | if (host != null && port != -1) { 83 | mHostEditText.setText(host); 84 | mPortEditText.setText(port + ""); 85 | } 86 | if (isAuthorized) { 87 | setButtonSubmitClick(); 88 | } 89 | 90 | return v; 91 | } 92 | 93 | @OnClick(R.id.submit) 94 | public void setButtonSubmitClick() { 95 | mHost = mHostEditText.getText().toString(); 96 | if (!mPortEditText.getText().toString().isEmpty() && !mHost.isEmpty()) { 97 | mPort = Integer.valueOf(mPortEditText.getText().toString()); 98 | ConnectionInfoProvider.saveConnectionInfo(getActivity(), mHost, mPort); 99 | mButtonSubmit.setVisibility(View.INVISIBLE); 100 | mSpinner.setVisibility(View.VISIBLE); 101 | mHostEditText.setEnabled(false); 102 | mPortEditText.setEnabled(false); 103 | Context context = getActivity().getApplicationContext(); 104 | UserConnectionsManager.get().addConnection(context, mHost, mPort, this); 105 | } 106 | } 107 | 108 | @OnClick(R.id.installation_guide) 109 | public void showInstallationGuide() { 110 | Intent i = new Intent(Intent.ACTION_VIEW); 111 | i.setData(Uri.parse(getString(R.string.installation_guide_link))); 112 | startActivity(Intent.createChooser(i, getString(R.string.installation_chooser))); 113 | } 114 | 115 | @Override 116 | public void onConnectionEstablished(final int connectionID) { 117 | if (mIsUIVisible) { 118 | mButtonSubmit.setVisibility(View.VISIBLE); 119 | mSpinner.setVisibility(View.INVISIBLE); 120 | mHostEditText.setEnabled(true); 121 | mPortEditText.setEnabled(true); 122 | } 123 | 124 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity 125 | ().getApplicationContext()); 126 | SharedPreferences.Editor editor = sharedPref.edit(); 127 | editor.putString(getString(R.string.shared_preferences_host), mHost); 128 | editor.putInt(getString(R.string.shared_preferences_port), mPort); 129 | editor.putBoolean(getString(R.string.shared_preferences_is_authorized), true); 130 | editor.apply(); 131 | 132 | Intent intent = new Intent(getActivity(), MainActivity.class); 133 | intent.putExtra(MainActivity.EXTRA_CONNECTION_ID, connectionID); 134 | intent.putExtra(MainActivity.EXTRA_HOST_NAME, mHost); 135 | intent.putExtra(MainActivity.EXTRA_PORT, mPort); 136 | startActivity(intent); 137 | } 138 | 139 | @Override 140 | public void onConnectionError() { 141 | if (mIsUIVisible) { 142 | mButtonSubmit.setVisibility(View.VISIBLE); 143 | mSpinner.setVisibility(View.INVISIBLE); 144 | mHostEditText.setEnabled(true); 145 | mPortEditText.setEnabled(true); 146 | Toast.makeText(getContext(), getString(R.string.connection_error), Toast 147 | .LENGTH_SHORT).show(); 148 | } 149 | } 150 | 151 | @Override 152 | public void onDestroyView() { 153 | super.onDestroyView(); 154 | mUnbinder.unbind(); 155 | } 156 | 157 | @Override 158 | public void onPause() { 159 | super.onPause(); 160 | mIsUIVisible = false; 161 | } 162 | 163 | @Override 164 | public void onResume() { 165 | super.onResume(); 166 | mIsUIVisible = true; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/files/DownloadFileDialog.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities.files; 2 | 3 | 4 | import android.app.Dialog; 5 | import android.content.DialogInterface; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.design.widget.TextInputEditText; 10 | import android.support.v4.app.DialogFragment; 11 | import android.support.v7.app.AlertDialog; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | 15 | import com.metarhia.metacom.R; 16 | import com.metarhia.metacom.interfaces.DownloadFileByCodeListener; 17 | 18 | import butterknife.ButterKnife; 19 | 20 | 21 | /** 22 | * @author MariaKokshaikina 23 | */ 24 | public class DownloadFileDialog extends DialogFragment { 25 | 26 | public final static String DownloadFileDialogTag = "DownloadFileDialogTag"; 27 | 28 | private DownloadFileByCodeListener mListener = null; 29 | 30 | @NonNull 31 | @Override 32 | public Dialog onCreateDialog(Bundle savedInstanceState) { 33 | 34 | LayoutInflater layoutInflater = getActivity().getLayoutInflater(); 35 | 36 | final View view = layoutInflater.inflate(R.layout.fragment_download_code_dialog, null); 37 | 38 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) 39 | .setView(view) 40 | .setPositiveButton(getResources().getString(R.string.download), 41 | new DialogInterface.OnClickListener() { 42 | public void onClick(DialogInterface dialog, int whichButton) { 43 | TextInputEditText code = ButterKnife.findById(view, R.id.file_code); 44 | String fileCode = code.getText().toString(); 45 | mListener.downloadByCode(fileCode); 46 | } 47 | } 48 | ) 49 | .setNegativeButton(getResources().getString(R.string.cancel), 50 | new DialogInterface.OnClickListener() { 51 | public void onClick(DialogInterface dialog, int whichButton) { 52 | dialog.dismiss(); 53 | } 54 | } 55 | ); 56 | 57 | Dialog dialog = builder.create(); 58 | dialog.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R 59 | .color.black14))); 60 | return dialog; 61 | } 62 | 63 | public void setDownloadFileByCodeListener(DownloadFileByCodeListener listener) { 64 | mListener = listener; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/files/FilesFragment.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities.files; 2 | 3 | 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.os.Environment; 10 | import android.provider.MediaStore; 11 | import android.support.annotation.NonNull; 12 | import android.support.v4.app.DialogFragment; 13 | import android.support.v4.app.Fragment; 14 | import android.support.v4.content.FileProvider; 15 | import android.support.v4.content.res.ResourcesCompat; 16 | import android.view.ContextMenu; 17 | import android.view.LayoutInflater; 18 | import android.view.MenuItem; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.webkit.MimeTypeMap; 22 | import android.widget.ImageView; 23 | import android.widget.TextView; 24 | import android.widget.Toast; 25 | 26 | import com.metarhia.metacom.BuildConfig; 27 | import com.metarhia.metacom.R; 28 | import com.metarhia.metacom.interfaces.DownloadFileByCodeListener; 29 | import com.metarhia.metacom.interfaces.FileDownloadedListener; 30 | import com.metarhia.metacom.interfaces.FileUploadedCallback; 31 | import com.metarhia.metacom.models.FilesManager; 32 | import com.metarhia.metacom.models.UserConnectionsManager; 33 | import com.metarhia.metacom.utils.PermissionUtils; 34 | 35 | import java.io.File; 36 | import java.io.FileNotFoundException; 37 | import java.io.InputStream; 38 | 39 | import butterknife.BindView; 40 | import butterknife.ButterKnife; 41 | import butterknife.OnClick; 42 | import butterknife.Unbinder; 43 | 44 | import static com.metarhia.metacom.activities.files.DownloadFileDialog.DownloadFileDialogTag; 45 | import static com.metarhia.metacom.activities.files.UploadFileDialog.UploadFileDialogTag; 46 | 47 | 48 | /** 49 | * @author MariaKokshaikina 50 | */ 51 | public class FilesFragment extends Fragment implements FileDownloadedListener, 52 | FileUploadedCallback, DownloadFileByCodeListener { 53 | 54 | private static final String KEY_CONNECTION_ID = "keyConnectionId"; 55 | private static final String KEY_BOTTOM_NOTICE = "keyBottomNotice"; 56 | private static final String KEY_BOTTOM_MESSAGE = "keyBottomMessage"; 57 | private static final String KEY_OPEN_FILE = "keyOpenFile"; 58 | private static final String KEY_FILE_URI = "keyFileUri"; 59 | private static final String TMP_METACOM_JPG = "/tmp-metacom.jpg"; 60 | private static final String AUTHORITY_STRING = BuildConfig.APPLICATION_ID + ".provider"; 61 | 62 | private static final int PICK_IMAGE_FROM_EXPLORER = 0; 63 | private static final int PICK_IMAGE_FROM_CAMERA = 1; 64 | private static final int TAKE_PHOTO = 2; 65 | private static final int FILE_EXPLORER = 3; 66 | 67 | @BindView(R.id.bottom_notice_text) 68 | TextView mBottomNoticeText; 69 | @BindView(R.id.bottom_notice_layout) 70 | View mBottomNoticeLayout; 71 | @BindView(R.id.upload_file) 72 | ImageView mUploadFile; 73 | @BindView(R.id.download_file) 74 | ImageView mDownloadFile; 75 | private Unbinder mUnbinder; 76 | 77 | private FilesManager mFilesManager; 78 | private boolean mBottomNotice; 79 | private boolean mOpenFile; 80 | private String mFilePath; 81 | private boolean isUIVisible; 82 | private boolean showUploadDialog; 83 | private String showUploadDialogCode; 84 | 85 | public static FilesFragment newInstance(int connectionID) { 86 | Bundle args = new Bundle(); 87 | args.putInt(KEY_CONNECTION_ID, connectionID); 88 | FilesFragment fragment = new FilesFragment(); 89 | fragment.setArguments(args); 90 | return fragment; 91 | } 92 | 93 | @Override 94 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 95 | Bundle savedInstanceState) { 96 | 97 | View view = inflater.inflate(R.layout.fragment_files, container, false); 98 | mUnbinder = ButterKnife.bind(this, view); 99 | 100 | setRetainInstance(true); 101 | 102 | registerForContextMenu(mUploadFile); 103 | 104 | if (getArguments() != null) { 105 | int connectionID = getArguments().getInt(KEY_CONNECTION_ID); 106 | mFilesManager = UserConnectionsManager.get().getConnection(connectionID) 107 | .getFilesManager(); 108 | } 109 | 110 | if (savedInstanceState != null) { 111 | if (savedInstanceState.getBoolean(KEY_BOTTOM_NOTICE)) { 112 | setBottomNoticeMessage(savedInstanceState.getString(KEY_BOTTOM_MESSAGE)); 113 | } 114 | if (savedInstanceState.getBoolean(KEY_OPEN_FILE)) { 115 | String fileUri = savedInstanceState.getString(KEY_FILE_URI); 116 | onFileDownloaded(fileUri); 117 | } 118 | } 119 | 120 | return view; 121 | } 122 | 123 | public void hideBottomNotice() { 124 | mBottomNotice = false; 125 | mBottomNoticeLayout.setVisibility(View.GONE); 126 | } 127 | 128 | public void setBottomNoticeMessage(String message) { 129 | mBottomNotice = true; 130 | mBottomNoticeLayout.setVisibility(View.VISIBLE); 131 | mBottomNoticeText.setText(message); 132 | } 133 | 134 | @OnClick(R.id.download_file) 135 | public void onDownloadFileClick() { 136 | if (PermissionUtils.checkIfAlreadyHavePermission(getContext())) { 137 | DownloadFileDialog dialog = new DownloadFileDialog(); 138 | dialog.setDownloadFileByCodeListener(this); 139 | dialog.show(getActivity().getSupportFragmentManager(), DownloadFileDialogTag); 140 | } else { 141 | if (PermissionUtils.checkVersion()) { 142 | PermissionUtils.requestForStoragePermission(this); 143 | } 144 | } 145 | } 146 | 147 | @OnClick(R.id.upload_file) 148 | public void onUploadFileClick() { 149 | if (PermissionUtils.checkIfAlreadyHavePermission(getContext())) { 150 | showFileChooser(); 151 | } else { 152 | if (PermissionUtils.checkVersion()) { 153 | PermissionUtils.requestForStoragePermission(this); 154 | } 155 | } 156 | } 157 | 158 | private void showForbidDialog() { 159 | if (isUIVisible) { 160 | Toast.makeText(getContext(), getString(R.string.permissions_are_not_granted), 161 | Toast.LENGTH_SHORT).show(); 162 | } 163 | } 164 | 165 | @Override 166 | public void onFileDownloaded(final String filePath) { 167 | setBottomNoticeMessage(getString(R.string.complete)); 168 | mDownloadFile.setEnabled(true); 169 | mDownloadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable 170 | .ic_file_download_24dp, null)); 171 | mOpenFile = true; 172 | mFilePath = filePath; 173 | mBottomNoticeLayout.setOnClickListener(new View.OnClickListener() { 174 | @Override 175 | public void onClick(View view) { 176 | openFile(mFilePath); 177 | mOpenFile = false; 178 | mBottomNoticeLayout.setOnClickListener(null); 179 | hideBottomNotice(); 180 | } 181 | }); 182 | } 183 | 184 | private void openFile(String filePath) { 185 | Uri uri = FileProvider.getUriForFile(getActivity(), 186 | BuildConfig.APPLICATION_ID + ".provider", 187 | new File(filePath)); 188 | 189 | String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); 190 | String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension 191 | (fileExtension); 192 | 193 | Intent intent = new Intent(Intent.ACTION_VIEW); 194 | intent.setDataAndType(uri, mimeType); 195 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 196 | startActivity(Intent.createChooser(intent, getString(R.string.open_file))); 197 | } 198 | 199 | @Override 200 | public void onFileDownloadError() { 201 | if (isUIVisible) { 202 | Toast.makeText(getContext(), getString(R.string.download_failed), Toast.LENGTH_SHORT) 203 | .show(); 204 | } 205 | hideBottomNotice(); 206 | mDownloadFile.setEnabled(true); 207 | mDownloadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable 208 | .ic_file_download_24dp, null)); 209 | } 210 | 211 | @Override 212 | public void onFileUploaded(final String fileCode) { 213 | if (isUIVisible) { 214 | showUploadDialog = false; 215 | hideBottomNotice(); 216 | DialogFragment dialog = UploadFileDialog.newInstance(fileCode); 217 | dialog.show(getActivity().getSupportFragmentManager(), UploadFileDialogTag); 218 | } else { 219 | showUploadDialog = true; 220 | showUploadDialogCode = fileCode; 221 | } 222 | mUploadFile.setEnabled(true); 223 | mUploadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable 224 | .ic_file_upload_24dp, null)); 225 | } 226 | 227 | @Override 228 | public void onFileUploadError(final String message) { 229 | if (isUIVisible) { 230 | Toast.makeText(getContext(), getString(R.string.err_upload_failed), Toast 231 | .LENGTH_SHORT).show(); 232 | } 233 | hideBottomNotice(); 234 | mUploadFile.setEnabled(true); 235 | mUploadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable 236 | .ic_file_upload_24dp, null)); 237 | } 238 | 239 | @Override 240 | public void onDestroyView() { 241 | super.onDestroyView(); 242 | mUnbinder.unbind(); 243 | } 244 | 245 | @Override 246 | public void downloadByCode(String code) { 247 | setBottomNoticeMessage(getString(R.string.downloading_dots)); 248 | mFilesManager.downloadFile(code, this); 249 | mDownloadFile.setEnabled(false); 250 | mDownloadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable 251 | .ic_file_download_grey_24dp, null)); 252 | } 253 | 254 | @Override 255 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 256 | super.onActivityResult(requestCode, resultCode, data); 257 | 258 | if ((requestCode == PICK_IMAGE_FROM_EXPLORER || requestCode == PICK_IMAGE_FROM_CAMERA) && 259 | resultCode == Activity.RESULT_OK) { 260 | Uri fileUri = null; 261 | switch (requestCode) { 262 | case PICK_IMAGE_FROM_EXPLORER: { 263 | fileUri = data.getData(); 264 | break; 265 | } 266 | case PICK_IMAGE_FROM_CAMERA: { 267 | File f = new File(Environment.getExternalStorageDirectory() + TMP_METACOM_JPG); 268 | fileUri = FileProvider.getUriForFile(getContext(), AUTHORITY_STRING, f); 269 | break; 270 | } 271 | } 272 | try { 273 | InputStream is = getActivity().getContentResolver().openInputStream(fileUri); 274 | mFilesManager.uploadFile(is, this); 275 | setBottomNoticeMessage(getString(R.string.uploading_dots)); 276 | mUploadFile.setEnabled(false); 277 | mUploadFile.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R 278 | .drawable.ic_file_upload_grey_24dp, null)); 279 | } catch (FileNotFoundException e) { 280 | e.printStackTrace(); 281 | } 282 | } 283 | } 284 | 285 | private void showFileChooser() { 286 | getActivity().openContextMenu(mUploadFile); 287 | } 288 | 289 | @Override 290 | public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo 291 | menuInfo) { 292 | if (v.getId() == mUploadFile.getId()) { 293 | menu.add(0, TAKE_PHOTO, 0, R.string.take_photo); 294 | menu.add(0, FILE_EXPLORER, 0, R.string.file_explorer); 295 | } 296 | } 297 | 298 | @Override 299 | public boolean onContextItemSelected(MenuItem item) { 300 | switch (item.getItemId()) { 301 | case TAKE_PHOTO: 302 | Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 303 | File f = new File(Environment.getExternalStorageDirectory() + TMP_METACOM_JPG); 304 | Uri uri = FileProvider.getUriForFile(getContext(), AUTHORITY_STRING, f); 305 | takePictureIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, uri); 306 | takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 307 | if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) { 308 | startActivityForResult(takePictureIntent, PICK_IMAGE_FROM_CAMERA); 309 | } 310 | return true; 311 | case FILE_EXPLORER: 312 | Intent intent = new Intent(); 313 | intent.setType("*/*"); 314 | intent.setAction(Intent.ACTION_GET_CONTENT); 315 | startActivityForResult(Intent.createChooser(intent, getString(R.string 316 | .select_file)), PICK_IMAGE_FROM_EXPLORER); 317 | return true; 318 | default: 319 | return super.onContextItemSelected(item); 320 | } 321 | } 322 | 323 | @Override 324 | public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], 325 | @NonNull int[] grantResults) { 326 | switch (requestCode) { 327 | case PermissionUtils.REQUEST_CODE: { 328 | if (grantResults.length > 0 329 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 330 | showFileChooser(); 331 | } else { 332 | showForbidDialog(); 333 | } 334 | } 335 | } 336 | } 337 | 338 | @Override 339 | public void onSaveInstanceState(Bundle outState) { 340 | super.onSaveInstanceState(outState); 341 | if (mBottomNotice) { 342 | outState.putBoolean(KEY_BOTTOM_NOTICE, mBottomNotice); 343 | outState.putString(KEY_BOTTOM_MESSAGE, mBottomNoticeText.getText().toString()); 344 | } 345 | if (mOpenFile) { 346 | outState.putBoolean(KEY_OPEN_FILE, mOpenFile); 347 | outState.putString(KEY_FILE_URI, mFilePath); 348 | } 349 | } 350 | 351 | @Override 352 | public void onPause() { 353 | super.onPause(); 354 | isUIVisible = false; 355 | } 356 | 357 | @Override 358 | public void onResume() { 359 | super.onResume(); 360 | isUIVisible = true; 361 | if (showUploadDialog) { 362 | onFileUploaded(showUploadDialogCode); 363 | } 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/activities/files/UploadFileDialog.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.activities.files; 2 | 3 | 4 | import android.app.Dialog; 5 | import android.content.DialogInterface; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.v4.app.DialogFragment; 10 | import android.support.v7.app.AlertDialog; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | 16 | import com.metarhia.metacom.R; 17 | 18 | import butterknife.BindView; 19 | import butterknife.ButterKnife; 20 | import butterknife.Unbinder; 21 | 22 | import static com.metarhia.metacom.utils.TextUtils.copyToClipboard; 23 | 24 | /** 25 | * @author MariaKokshaikina 26 | */ 27 | public class UploadFileDialog extends DialogFragment { 28 | 29 | public final static String UploadFileDialogTag = "UploadFileDialogTag"; 30 | public final static String KEY_UPLOAD_FILE_CODE = "KEY_UPLOAD_FILE_CODE"; 31 | 32 | @BindView(R.id.upload_result_string) 33 | TextView mUploadResultString; 34 | private Unbinder mUnbinder; 35 | 36 | private boolean isUIVisible = true; 37 | 38 | public static UploadFileDialog newInstance(String fileCode) { 39 | Bundle args = new Bundle(); 40 | args.putString(KEY_UPLOAD_FILE_CODE, fileCode); 41 | 42 | UploadFileDialog fragment = new UploadFileDialog(); 43 | fragment.setArguments(args); 44 | return fragment; 45 | } 46 | 47 | @NonNull 48 | @Override 49 | public Dialog onCreateDialog(Bundle savedInstanceState) { 50 | LayoutInflater layoutInflater = getActivity().getLayoutInflater(); 51 | 52 | View view = layoutInflater.inflate(R.layout.fragment_upload_file_dialog, null); 53 | mUnbinder = ButterKnife.bind(this, view); 54 | final String code = getArguments().getString(KEY_UPLOAD_FILE_CODE); 55 | mUploadResultString.setText(String.format(getResources().getString(R.string.upload_desc), 56 | code)); 57 | mUploadResultString.setOnLongClickListener(new View.OnLongClickListener() { 58 | @Override 59 | public boolean onLongClick(View view) { 60 | copyToClipboard(getActivity(), code); 61 | if (isUIVisible) { 62 | Toast.makeText(getContext(), getString(R.string.copied_code), Toast 63 | .LENGTH_SHORT).show(); 64 | } 65 | return true; 66 | } 67 | }); 68 | 69 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) 70 | .setView(view) 71 | .setTitle(R.string.upload) 72 | .setPositiveButton(getResources().getString(R.string.ok), 73 | new DialogInterface.OnClickListener() { 74 | public void onClick(DialogInterface dialog, int whichButton) { 75 | dismiss(); 76 | } 77 | } 78 | ) 79 | .setNegativeButton(getString(R.string.copy), new DialogInterface.OnClickListener() { 80 | @Override 81 | public void onClick(DialogInterface dialogInterface, int i) { 82 | dismiss(); 83 | copyToClipboard(getActivity(), code); 84 | if (isUIVisible) { 85 | Toast.makeText(getContext(), getString(R.string.copied_code), Toast 86 | .LENGTH_SHORT).show(); 87 | } 88 | } 89 | }); 90 | 91 | Dialog dialog = builder.create(); 92 | dialog.getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R 93 | .color.black14))); 94 | return dialog; 95 | } 96 | 97 | @Override 98 | public void onDestroyView() { 99 | super.onDestroyView(); 100 | mUnbinder.unbind(); 101 | } 102 | 103 | @Override 104 | public void onPause() { 105 | super.onPause(); 106 | isUIVisible = false; 107 | } 108 | 109 | @Override 110 | public void onResume() { 111 | super.onResume(); 112 | isUIVisible = true; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/connection/AndroidJSTPConnection.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.connection; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.net.ConnectivityManager; 8 | import android.net.wifi.WifiManager; 9 | import android.support.v4.content.LocalBroadcastManager; 10 | 11 | import com.metarhia.jstp.connection.Connection; 12 | import com.metarhia.jstp.connection.ConnectionListener; 13 | import com.metarhia.jstp.connection.Message; 14 | import com.metarhia.jstp.connection.RestorationPolicy; 15 | import com.metarhia.jstp.core.Handlers.ManualHandler; 16 | import com.metarhia.jstp.core.JSInterfaces.JSObject; 17 | import com.metarhia.jstp.transport.TCPTransport; 18 | import com.metarhia.metacom.utils.Constants; 19 | import com.metarhia.metacom.utils.NetworkUtils; 20 | 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Queue; 24 | import java.util.UUID; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | import java.util.concurrent.CopyOnWriteArrayList; 27 | 28 | /** 29 | * JSTP connection wrapper for Android 30 | * 31 | * @author lidaamber 32 | * @author lundibundi 33 | */ 34 | 35 | public class AndroidJSTPConnection implements ConnectionListener, RestorationPolicy { 36 | 37 | private static final String DEFAULT_CACHE = "defaultCacheTag"; 38 | 39 | private static final int STATE_NOT_CONNECTED = 10; 40 | private static final int STATE_CONNECTING = 427; 41 | private static final int STATE_CONNECTED = 500; 42 | 43 | private final LocalBroadcastManager mBroadcastManager; 44 | 45 | private String mApplicationName; 46 | 47 | private final Connection mConnection; 48 | 49 | private final Context mContext; 50 | 51 | private final ConcurrentHashMap> mTaggedCacheCalls; 52 | 53 | private int mConnectionState; 54 | 55 | private final List mListeners; 56 | private boolean mNeedsRestoration; 57 | 58 | public AndroidJSTPConnection(String host, int port, boolean usesSSL, Context context) { 59 | mListeners = new CopyOnWriteArrayList<>(); 60 | mTaggedCacheCalls = new ConcurrentHashMap<>(); 61 | mConnectionState = STATE_NOT_CONNECTED; 62 | 63 | mNeedsRestoration = true; 64 | mContext = context; 65 | 66 | TCPTransport transport = new TCPTransport(host, port, usesSSL); 67 | mConnection = new Connection(transport, this); 68 | mConnection.addSocketListener(this); 69 | 70 | mBroadcastManager = LocalBroadcastManager.getInstance(mContext); 71 | 72 | initNetworkReceiver(); 73 | } 74 | 75 | private void initNetworkReceiver() { 76 | IntentFilter networkActionsFilter = 77 | new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 78 | networkActionsFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 79 | 80 | BroadcastReceiver networkActionsReceiver = new BroadcastReceiver() { 81 | @Override 82 | public void onReceive(Context context, Intent intent) { 83 | if (mApplicationName == null) return; 84 | 85 | else if (!isConnected()) openConnection(mApplicationName); 86 | } 87 | }; 88 | mContext.registerReceiver(networkActionsReceiver, networkActionsFilter); 89 | } 90 | 91 | public void openConnection(final String applicationName) { 92 | if (mConnectionState != STATE_NOT_CONNECTED) return; 93 | mConnectionState = STATE_CONNECTING; 94 | 95 | mNeedsRestoration = true; 96 | mApplicationName = applicationName; 97 | checkStartConnection(); 98 | } 99 | 100 | @Override 101 | public boolean restore(Connection connection, Queue queue) { 102 | return false; 103 | } 104 | 105 | @Override 106 | public void onTransportAvailable(Connection jstpConnection, String appName, 107 | String sessionID) { 108 | jstpConnection.handshake(appName, new ManualHandler() { 109 | @Override 110 | public void handle(JSObject jsValue) { 111 | mConnectionState = STATE_CONNECTED; 112 | notifyHasConnection(); 113 | 114 | if (mListeners != null) { 115 | sendCachedCalls(); 116 | for (AndroidJSTPConnectionListener l : mListeners) { 117 | l.onConnectionEstablished(AndroidJSTPConnection.this); 118 | } 119 | } 120 | } 121 | }); 122 | } 123 | 124 | private void checkStartConnection() { 125 | if (checkConnection()) { 126 | mConnectionState = STATE_CONNECTING; 127 | mConnection.connect(mApplicationName); 128 | } else { 129 | mConnectionState = STATE_NOT_CONNECTED; 130 | } 131 | } 132 | 133 | private boolean checkConnection() { 134 | return NetworkUtils.isConnectedWifi(mContext) 135 | || isConnectedFast(); 136 | } 137 | 138 | private void sendCachedCalls() { 139 | sendCachedCalls(DEFAULT_CACHE); 140 | } 141 | 142 | private void sendCachedCalls(final String tag) { 143 | ConcurrentHashMap cacheCalls = mTaggedCacheCalls.get(tag); 144 | if (cacheCalls == null) return; 145 | 146 | for (final Map.Entry callData : cacheCalls.entrySet()) { 147 | sendCachedCall(tag, callData.getKey(), callData.getValue()); 148 | } 149 | } 150 | 151 | private void sendCachedCall(final String tag, final UUID uuid, final CacheCallData callData) { 152 | mConnection.call(callData.mInterfaceName, 153 | callData.mMethodName, 154 | callData.mArgs, 155 | new ManualHandler() { 156 | @Override 157 | public void handle(JSObject message) { 158 | CacheCallData callData = mTaggedCacheCalls.get(tag).remove(uuid); 159 | callData.mManualHandler.handle(message); 160 | } 161 | }); 162 | } 163 | 164 | private boolean isConnected() { 165 | return mConnectionState == STATE_CONNECTED; 166 | } 167 | 168 | @Override 169 | public void onConnectionClosed() { 170 | reportConnectionLost(); 171 | if (isConnectedFast() && mNeedsRestoration) openConnection(mApplicationName); 172 | else if (needsConnection()) notifyNeedsConnection(); 173 | } 174 | 175 | public boolean removeCachedCall(UUID uuid) { 176 | return removeCachedCall(DEFAULT_CACHE, uuid); 177 | } 178 | 179 | private boolean removeCachedCall(String cacheName, UUID uuid) { 180 | if (uuid == null) return false; 181 | ConcurrentHashMap cachedCalls = mTaggedCacheCalls.get(cacheName); 182 | return cachedCalls != null && cachedCalls.remove(uuid) != null; 183 | } 184 | 185 | public void removeCachedCalls(String cacheName) { 186 | ConcurrentHashMap cachedCalls = mTaggedCacheCalls.get(cacheName); 187 | if (cachedCalls != null) cachedCalls.clear(); 188 | } 189 | 190 | public void close() { 191 | mNeedsRestoration = false; 192 | mConnection.close(); 193 | } 194 | 195 | public interface AndroidJSTPConnectionListener { 196 | void onConnectionEstablished(AndroidJSTPConnection connection); 197 | 198 | void onConnectionLost(); 199 | } 200 | 201 | public UUID cacheCall(String interfaceName, final String methodName, List args, 202 | final ManualHandler handler) { 203 | return cacheCall(DEFAULT_CACHE, interfaceName, methodName, args, handler); 204 | } 205 | 206 | private UUID cacheCall(final String cacheTag, String interfaceName, final String methodName, 207 | List args, final ManualHandler handler) { 208 | ConcurrentHashMap cachedCalls = mTaggedCacheCalls.get(cacheTag); 209 | if (cachedCalls == null) { 210 | cachedCalls = new ConcurrentHashMap<>(); 211 | mTaggedCacheCalls.put(cacheTag, cachedCalls); 212 | } 213 | CacheCallData cacheCallData = new CacheCallData(interfaceName, methodName, args, handler); 214 | final UUID uuid = UUID.randomUUID(); 215 | cachedCalls.put(uuid, cacheCallData); 216 | if (isConnected()) { 217 | sendCachedCall(cacheTag, uuid, cacheCallData); 218 | } else if (!NetworkUtils.isConnected(mContext)) { 219 | notifyNeedsConnection(); 220 | } 221 | return uuid; 222 | } 223 | 224 | private void reportConnectionLost() { 225 | mConnectionState = STATE_NOT_CONNECTED; 226 | for (AndroidJSTPConnectionListener listener : mListeners) { 227 | listener.onConnectionLost(); 228 | } 229 | } 230 | 231 | private void notifyNeedsConnection() { 232 | mBroadcastManager.sendBroadcast(new Intent(Constants.ACTION_NEEDS_CONNECTION)); 233 | } 234 | 235 | private void notifyHasConnection() { 236 | mBroadcastManager.sendBroadcast(new Intent(Constants.ACTION_HAS_CONNECTION)); 237 | } 238 | 239 | private boolean needsConnection() { 240 | for (Map.Entry> me : 241 | mTaggedCacheCalls.entrySet()) { 242 | if (me.getValue().size() != 0) return true; 243 | } 244 | return false; 245 | } 246 | 247 | public void event(String interfaceName, String methodName, List args) { 248 | mConnection.event(interfaceName, methodName, args); 249 | } 250 | 251 | public void addCallHandler(String interfaceName, String methodName, ManualHandler callHandler) { 252 | mConnection.setCallHandler(interfaceName, methodName, callHandler); 253 | } 254 | 255 | public void addEventHandler(String interfaceName, String eventName, ManualHandler handler) { 256 | mConnection.addEventHandler(interfaceName, eventName, handler); 257 | } 258 | 259 | private boolean isConnectedFast() { 260 | return NetworkUtils.isConnectedFast(mContext); 261 | } 262 | 263 | @Override 264 | public void onConnected(boolean restored) { 265 | } 266 | 267 | @Override 268 | public void onMessageRejected(JSObject jsObject) { 269 | 270 | } 271 | 272 | @Override 273 | public void onConnectionError(int i) { 274 | } 275 | 276 | public void addListener(AndroidJSTPConnectionListener listener) { 277 | mListeners.add(listener); 278 | } 279 | 280 | public void removeEventHandler(String interfaceName, String methodName, ManualHandler handler) { 281 | mConnection.removeEventHandler(interfaceName, methodName, handler); 282 | } 283 | public void removeListener(AndroidJSTPConnectionListener listener) { 284 | mListeners.remove(listener); 285 | } 286 | 287 | private static class CacheCallData { 288 | 289 | final String mInterfaceName; 290 | final String mMethodName; 291 | final List mArgs; 292 | final ManualHandler mManualHandler; 293 | 294 | CacheCallData(String interfaceName, String methodName, List args, 295 | ManualHandler manualHandler) { 296 | this.mArgs = args; 297 | this.mInterfaceName = interfaceName; 298 | this.mMethodName = methodName; 299 | this.mManualHandler = manualHandler; 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/connection/Errors.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.connection; 2 | 3 | import android.content.res.Resources; 4 | 5 | import com.metarhia.metacom.R; 6 | 7 | /** 8 | * Error codes holder 9 | * 10 | * @author lidaamber 11 | */ 12 | 13 | public class Errors { 14 | 15 | /** 16 | * Local error codes 17 | */ 18 | public static final int ERR_FILE_LOAD = 1; 19 | 20 | /** 21 | * API error codes 22 | */ 23 | private static final int ERR_ROOM_TAKEN = 30; 24 | private static final int ERR_NOT_IN_CHAT = 31; 25 | private static final int ERR_NO_INTERLOCUTOR = 32; 26 | private static final int ERR_NO_SUCH_FILE = 33; 27 | private static final int ERR_UPLOAD_NOT_STARTED = 34; 28 | private static final int ERR_PREVIOUS_UPLOAD_NOT_FINISHED = 35; 29 | 30 | /** 31 | * Application resources 32 | */ 33 | private static Resources sResources; 34 | 35 | /** 36 | * Initializes utils class with app resources 37 | * 38 | * @param resources app resources 39 | */ 40 | public static void initResources(Resources resources) { 41 | sResources = resources; 42 | } 43 | 44 | /** 45 | * Gets localized description of error 46 | * 47 | * @param code error code 48 | * @return description of error 49 | */ 50 | public static String getErrorByCode(Integer code) { 51 | switch (code) { 52 | case ERR_ROOM_TAKEN: 53 | return sResources.getString(R.string.err_room_taken); 54 | case ERR_NOT_IN_CHAT: 55 | return sResources.getString(R.string.err_not_in_chat); 56 | case ERR_NO_INTERLOCUTOR: 57 | return sResources.getString(R.string.no_interlocutor); 58 | case ERR_NO_SUCH_FILE: 59 | return sResources.getString(R.string.err_no_such_file); 60 | case ERR_FILE_LOAD: 61 | return sResources.getString(R.string.err_file_load); 62 | case ERR_UPLOAD_NOT_STARTED: 63 | return sResources.getString(R.string.err_upload_not_started); 64 | case ERR_PREVIOUS_UPLOAD_NOT_FINISHED: 65 | return sResources.getString(R.string.err_previous_upload_not_finished); 66 | default: 67 | return null; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/connection/OkErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.connection; 2 | 3 | import com.metarhia.jstp.compiler.annotations.handlers.Array; 4 | import com.metarhia.jstp.compiler.annotations.handlers.Handler; 5 | import com.metarhia.jstp.compiler.annotations.handlers.NotNull; 6 | import com.metarhia.jstp.compiler.annotations.handlers.Object; 7 | import com.metarhia.jstp.handlers.ExecutableHandler; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * JSTP handler 13 | * 14 | * @author lidaamber 15 | */ 16 | 17 | @Handler(ExecutableHandler.class) 18 | public interface OkErrorHandler { 19 | 20 | @NotNull 21 | @Object("ok") 22 | void onOk(List args); 23 | 24 | @NotNull 25 | @Object("error") 26 | void onError(@Array(0) Integer errorCode); 27 | } 28 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/BackPressedHandler.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | /** 4 | * Created by masha on 7/31/17. 5 | */ 6 | 7 | public interface BackPressedHandler { 8 | public void handleBackPress(); 9 | } 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/ChatReconnectionListener.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | /** 4 | * Reconnection interface for chat room 5 | * 6 | * @author lidaamber 7 | */ 8 | 9 | public interface ChatReconnectionListener { 10 | 11 | /** 12 | * Emitted when connection gets lost 13 | */ 14 | void onConnectionLost(); 15 | 16 | /** 17 | * Emitted when rejoin passes successfully 18 | */ 19 | void onRejoinSuccess(boolean hasInterlocutor); 20 | 21 | /** 22 | * Emitted when rejoin passes with error 23 | * 24 | * @param errorMessage error message 25 | */ 26 | void onRejoinError(String errorMessage); 27 | } 28 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/ConnectionCallback.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | /** 4 | * Callback after connection establishment attempt 5 | * 6 | * @author lidaamber 7 | */ 8 | 9 | public interface ConnectionCallback { 10 | 11 | /** 12 | * Called when connection is successfully established 13 | * 14 | * @param connectionID connection id 15 | */ 16 | void onConnectionEstablished(int connectionID); 17 | 18 | /** 19 | * Called when server responds error 20 | */ 21 | void onConnectionError(); 22 | } 23 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/DownloadFileByCodeListener.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | /** 4 | * @author MariaKokshaikina 5 | */ 6 | 7 | public interface DownloadFileByCodeListener { 8 | 9 | void downloadByCode(String code); 10 | } 11 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/FileDownloadedListener.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | /** 4 | * Listener for file downloaded event 5 | * 6 | * @author lidaamber 7 | */ 8 | 9 | public interface FileDownloadedListener { 10 | 11 | /** 12 | * Called when file is downloaded successfully 13 | * 14 | * @param path absolute path to file 15 | */ 16 | void onFileDownloaded(String path); 17 | 18 | /** 19 | * Called when server responds error 20 | */ 21 | void onFileDownloadError(); 22 | } 23 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/FileUploadedCallback.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | /** 4 | * Callback after file uploading 5 | * 6 | * @author lidaamber 7 | */ 8 | 9 | public interface FileUploadedCallback { 10 | 11 | /** 12 | * Called when file is uploaded successfully 13 | * 14 | * @param fileCode code of uploaded file 15 | */ 16 | void onFileUploaded(String fileCode); 17 | 18 | /** 19 | * Called when server responds error 20 | * 21 | * @param message error message 22 | */ 23 | void onFileUploadError(String message); 24 | } 25 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/JoinRoomCallback.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | /** 4 | * Callback after chat establishment attempt 5 | * 6 | * @author lidaamber 7 | */ 8 | 9 | public interface JoinRoomCallback { 10 | 11 | /** 12 | * Called when chat is established successfully 13 | */ 14 | void onJoinedRoom(boolean hasInterlocutor); 15 | 16 | /** 17 | * Called when server responds error 18 | * 19 | * @param errorMessage error message 20 | */ 21 | void onJoinError(String errorMessage); 22 | } 23 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/LeaveRoomCallback.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | /** 4 | * Callback after leaving chat room 5 | * 6 | * @author lidaamber 7 | */ 8 | 9 | public interface LeaveRoomCallback { 10 | 11 | /** 12 | * Called when user successfully left chat room 13 | */ 14 | void onLeavedRoom(); 15 | 16 | /** 17 | * Called when server responds error 18 | * 19 | * @param errorMessage error message 20 | */ 21 | void onLeaveError(String errorMessage); 22 | } 23 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/MessageListener.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | import com.metarhia.metacom.models.Message; 4 | 5 | /** 6 | * Incoming messages listener 7 | * 8 | * @author lidaamber 9 | */ 10 | 11 | public interface MessageListener { 12 | 13 | /** 14 | * Called when message is received 15 | * 16 | * @param message message received from server 17 | */ 18 | void onMessageReceived(Message message); 19 | } 20 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/interfaces/MessageSentCallback.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.interfaces; 2 | 3 | import com.metarhia.metacom.models.Message; 4 | 5 | /** 6 | * Callback after message sending 7 | * 8 | * @author lidaamber 9 | */ 10 | 11 | public interface MessageSentCallback { 12 | 13 | /** 14 | * Called when message is sent successfully 15 | * 16 | * @param message message sent by user 17 | */ 18 | void onMessageSent(Message message); 19 | 20 | 21 | /** 22 | * Called when server responds error 23 | * 24 | * @param message error message 25 | */ 26 | void onMessageSentError(String message); 27 | } 28 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/models/ChatRoom.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.models; 2 | 3 | import android.util.Base64; 4 | 5 | import com.metarhia.jstp.compiler.annotations.handlers.Array; 6 | import com.metarhia.jstp.core.Handlers.ManualHandler; 7 | import com.metarhia.jstp.core.JSInterfaces.JSObject; 8 | import com.metarhia.jstp.handlers.ExecutableHandler; 9 | import com.metarhia.metacom.connection.AndroidJSTPConnection; 10 | import com.metarhia.metacom.connection.Errors; 11 | import com.metarhia.metacom.connection.JSTPOkErrorHandler; 12 | import com.metarhia.metacom.interfaces.ChatReconnectionListener; 13 | import com.metarhia.metacom.interfaces.FileDownloadedListener; 14 | import com.metarhia.metacom.interfaces.FileUploadedCallback; 15 | import com.metarhia.metacom.interfaces.MessageListener; 16 | import com.metarhia.metacom.interfaces.MessageSentCallback; 17 | import com.metarhia.metacom.utils.Constants; 18 | import com.metarhia.metacom.utils.FileUtils; 19 | import com.metarhia.metacom.utils.MainExecutor; 20 | 21 | import java.io.InputStream; 22 | import java.util.ArrayList; 23 | import java.util.LinkedList; 24 | import java.util.List; 25 | import java.util.Queue; 26 | 27 | /** 28 | * MetaCom chat room 29 | * 30 | * @author lidaamber 31 | */ 32 | 33 | public class ChatRoom { 34 | 35 | /** 36 | * Chat room name 37 | */ 38 | private String mChatRoomName; 39 | 40 | /** 41 | * MetaCom connection 42 | */ 43 | private final AndroidJSTPConnection mConnection; 44 | 45 | /** 46 | * Message listener for incoming chat room messages 47 | */ 48 | 49 | private MessageListener mMessageListener; 50 | 51 | /** 52 | * Current downloaded file chunks 53 | */ 54 | private ArrayList mCurrentFileBuffer; 55 | 56 | /** 57 | * Current downloaded file extension 58 | */ 59 | private String mCurrentExtension; 60 | 61 | /** 62 | * Callback for file downloaded 63 | */ 64 | private FileDownloadedListener mFileDownloadedListener; 65 | 66 | /** 67 | * Chat reconnection listener 68 | */ 69 | private ChatReconnectionListener mChatReconnectionListener; 70 | 71 | /** 72 | * Has interlocutor state 73 | */ 74 | private boolean mHasInterlocutor; 75 | 76 | /** 77 | * File upload queue 78 | */ 79 | private Queue mFileQueue; 80 | 81 | private ManualHandler mStartHandler; 82 | private ManualHandler mChunkHandler; 83 | private ExecutableHandler mEndHandler; 84 | private ExecutableHandler mChatJoinHandler; 85 | private ExecutableHandler mChatLeaveHandler; 86 | private ExecutableHandler mMessageHandler; 87 | 88 | /** 89 | * Creates new chat room by name 90 | * 91 | * @param chatRoomName chat name 92 | */ 93 | ChatRoom(String chatRoomName, AndroidJSTPConnection connection) { 94 | mChatRoomName = chatRoomName; 95 | mConnection = connection; 96 | mCurrentFileBuffer = new ArrayList<>(); 97 | mFileQueue = new LinkedList<>(); 98 | 99 | initIncomingMessagesListener(); 100 | initChatJoinListener(); 101 | initChatLeaveListener(); 102 | initChatTransferListener(); 103 | } 104 | 105 | /** 106 | * Gets chat room name 107 | * 108 | * @return chat room name 109 | */ 110 | String getChatRoomName() { 111 | return mChatRoomName; 112 | } 113 | 114 | /** 115 | * Sends message to chat room 116 | * 117 | * @param sentMessage message to be sent 118 | * @param callback callback after message sending (success and error) 119 | */ 120 | public void sendMessage(final Message sentMessage, final MessageSentCallback callback) { 121 | List args = new ArrayList<>(); 122 | args.add(sentMessage.getContent()); 123 | mConnection.cacheCall(Constants.META_COM, "send", args, new JSTPOkErrorHandler(MainExecutor.get()) { 124 | @Override 125 | public void onOk(List args) { 126 | callback.onMessageSent(sentMessage); 127 | } 128 | 129 | @Override 130 | public void onError(Integer errorCode) { 131 | callback.onMessageSentError(Errors.getErrorByCode(errorCode)); 132 | } 133 | }); 134 | } 135 | 136 | /** 137 | * Adds new incoming messages listener to chat 138 | * 139 | * @param listener incoming messages listener 140 | */ 141 | public void setMessageListener(MessageListener listener) { 142 | mMessageListener = listener; 143 | } 144 | 145 | /** 146 | * Adds message event handler to JSTP connection 147 | */ 148 | private void initIncomingMessagesListener() { 149 | mMessageHandler = new ExecutableHandler(MainExecutor.get()) { 150 | @Override 151 | public void run() { 152 | List messagePayload = (List) (message).get("message"); 153 | String messageContent = (String) messagePayload.get(0); 154 | 155 | Message message = new Message(MessageType.TEXT, messageContent, true); 156 | if (mMessageListener != null) { 157 | mMessageListener.onMessageReceived(message); 158 | } 159 | } 160 | }; 161 | mConnection.addEventHandler(Constants.META_COM, "message", mMessageHandler); 162 | } 163 | 164 | private void initChatJoinListener() { 165 | mChatJoinHandler = new ExecutableHandler(MainExecutor.get()) { 166 | @Override 167 | public void run() { 168 | mHasInterlocutor = true; 169 | 170 | String infoText = Constants.EVENT_CHAT_JOIN; 171 | Message message = new Message(MessageType.INFO, infoText, true); 172 | if (mMessageListener != null) { 173 | mMessageListener.onMessageReceived(message); 174 | } 175 | } 176 | }; 177 | 178 | mConnection.addEventHandler(Constants.META_COM, "chatJoin", mChatJoinHandler); 179 | } 180 | 181 | private void initChatLeaveListener() { 182 | mFileQueue.clear(); 183 | mChatLeaveHandler = new ExecutableHandler(MainExecutor.get()) { 184 | @Override 185 | public void run() { 186 | mHasInterlocutor = false; 187 | 188 | String infoText = Constants.EVENT_CHAT_LEAVE; 189 | Message message = new Message(MessageType.INFO, infoText, true); 190 | if (mMessageListener != null) { 191 | mMessageListener.onMessageReceived(message); 192 | } 193 | } 194 | }; 195 | mConnection.addEventHandler(Constants.META_COM, "chatLeave", mChatLeaveHandler); 196 | } 197 | 198 | private void initChatTransferListener() { 199 | mStartHandler = new ManualHandler() { 200 | @Override 201 | public void handle(JSObject jsObject) { 202 | List messagePayload = (List) (jsObject).get("chatFileTransferStart"); 203 | String type = (String) messagePayload.get(0); 204 | mCurrentExtension = (type == null) ? "txt" : 205 | FileUtils.sMimeTypeMap.getExtensionFromMimeType(type); 206 | 207 | mCurrentFileBuffer = new ArrayList<>(); 208 | } 209 | }; 210 | 211 | mChunkHandler = new ManualHandler() { 212 | @Override 213 | public void handle(JSObject jsObject) { 214 | List messagePayload = (List) (jsObject).get("chatFileTransferChunk"); 215 | String fileChunk = (String) messagePayload.get(0); 216 | if (mCurrentFileBuffer != null) 217 | mCurrentFileBuffer.add(Base64.decode(fileChunk, Base64.NO_WRAP)); 218 | } 219 | }; 220 | 221 | mEndHandler = new ExecutableHandler(MainExecutor.get()) { 222 | @Override 223 | public void run() { 224 | saveFile(); 225 | } 226 | }; 227 | 228 | mConnection.addEventHandler(Constants.META_COM, "chatFileTransferStart", mStartHandler); 229 | mConnection.addEventHandler(Constants.META_COM, "chatFileTransferChunk", mChunkHandler); 230 | mConnection.addEventHandler(Constants.META_COM, "chatFileTransferEnd", mEndHandler); 231 | } 232 | 233 | void removeAllHandlers() { 234 | mFileQueue.clear(); 235 | 236 | mConnection.removeEventHandler(Constants.META_COM, "chatFileTransferStart", mStartHandler); 237 | mStartHandler = null; 238 | mConnection.removeEventHandler(Constants.META_COM, "chatFileTransferChunk", mChunkHandler); 239 | mChunkHandler = null; 240 | mConnection.removeEventHandler(Constants.META_COM, "chatFileTransferEnd", mEndHandler); 241 | mEndHandler = null; 242 | mConnection.removeEventHandler(Constants.META_COM, "chatJoin", mChatJoinHandler); 243 | mChatJoinHandler = null; 244 | mConnection.removeEventHandler(Constants.META_COM, "chatLeave", mChatLeaveHandler); 245 | mChatLeaveHandler = null; 246 | mConnection.removeEventHandler(Constants.META_COM, "message", mMessageHandler); 247 | mMessageHandler = null; 248 | } 249 | 250 | /** 251 | * Uploads file in chat 252 | * 253 | * @param fileStream file to upload 254 | * @param callback callback after file upload (success and error) 255 | */ 256 | public void uploadFile(final InputStream fileStream, String mimeType, 257 | final FileUploadedCallback callback) { 258 | if (!mFileQueue.isEmpty()) { 259 | mFileQueue.add(new FileUploadData(fileStream, mimeType, callback)); 260 | return; 261 | } 262 | 263 | mFileQueue.add(new FileUploadData(fileStream, mimeType, callback)); 264 | uploadNextFromQueue(); 265 | 266 | } 267 | 268 | private void uploadNextFromQueue() { 269 | if (mFileQueue.isEmpty()) return; 270 | final FileUploadData data = mFileQueue.peek(); 271 | startFileUpload(data.mimeType, new JSTPOkErrorHandler(MainExecutor.get()) { 272 | @Override 273 | public void onOk(List args) { 274 | FileUtils.uploadSplitFile(data.fileStream, 275 | new FileUtils.FileUploadingInterface() { 276 | @Override 277 | public void sendChunk(byte[] chunk, JSTPOkErrorHandler handler) { 278 | ChatRoom.this.sendChunk(chunk, handler); 279 | } 280 | 281 | @Override 282 | public void endFileUpload(FileUploadedCallback callback) { 283 | ChatRoom.this.endFileUpload(callback); 284 | 285 | } 286 | }, data.callback); 287 | } 288 | 289 | @Override 290 | public void onError(@Array(0) Integer errorCode) { 291 | data.callback.onFileUploadError(Errors.getErrorByCode(errorCode)); 292 | mFileQueue.remove(); 293 | uploadNextFromQueue(); 294 | } 295 | }); 296 | 297 | } 298 | 299 | /** 300 | * Starts file upload to server 301 | * 302 | * @param mimeType mime type of the sent file 303 | * @param handler JSTP handler 304 | */ 305 | private void startFileUpload(String mimeType, JSTPOkErrorHandler handler) { 306 | List args = new ArrayList<>(); 307 | args.add(mimeType); 308 | mConnection.cacheCall(Constants.META_COM, "startChatFileTransfer", args, handler); 309 | } 310 | 311 | /** 312 | * Sends chunk to server 313 | * 314 | * @param chunk chunk to send 315 | * @param handler JSTP handler 316 | */ 317 | private void sendChunk(byte[] chunk, JSTPOkErrorHandler handler) { 318 | List args = new ArrayList<>(); 319 | args.add(Base64.encodeToString(chunk, Base64.NO_WRAP)); 320 | mConnection.cacheCall(Constants.META_COM, "sendFileChunkToChat", args, handler); 321 | } 322 | 323 | /** 324 | * Ends file upload to server 325 | * 326 | * @param callback callback after ending file upload 327 | */ 328 | private void endFileUpload(final FileUploadedCallback callback) { 329 | mConnection.cacheCall(Constants.META_COM, "endChatFileTransfer", new ArrayList<>(), 330 | new JSTPOkErrorHandler(MainExecutor.get()) { 331 | @Override 332 | public void onOk(List args) { 333 | callback.onFileUploaded(null); 334 | mFileQueue.remove(); 335 | uploadNextFromQueue(); 336 | } 337 | 338 | @Override 339 | public void onError(Integer errorCode) { 340 | callback.onFileUploadError(Errors.getErrorByCode(errorCode)); 341 | mFileQueue.remove(); 342 | uploadNextFromQueue(); 343 | } 344 | }); 345 | } 346 | 347 | /** 348 | * Saves currently downloaded file to downloads folder 349 | */ 350 | private void saveFile() { 351 | FileUtils.saveFileInDownloads(String.valueOf(mCurrentExtension), 352 | new ArrayList<>(mCurrentFileBuffer), 353 | new FileDownloadedListener() { 354 | @Override 355 | public void onFileDownloaded(String path) { 356 | if (mFileDownloadedListener != null) { 357 | mFileDownloadedListener.onFileDownloaded(path); 358 | } 359 | } 360 | 361 | @Override 362 | public void onFileDownloadError() { 363 | if (mFileDownloadedListener != null) { 364 | mFileDownloadedListener.onFileDownloadError(); 365 | } 366 | } 367 | }); 368 | } 369 | 370 | /** 371 | * Sets listener on file downloaded event 372 | * 373 | * @param fileDownloadedListener file downloaded listener 374 | */ 375 | public void setFileDownloadedListener(FileDownloadedListener fileDownloadedListener) { 376 | mFileDownloadedListener = fileDownloadedListener; 377 | } 378 | 379 | void reportRejoinSuccess(boolean hasInterlocutor) { 380 | mHasInterlocutor = hasInterlocutor; 381 | if (mChatReconnectionListener != null) { 382 | mChatReconnectionListener.onRejoinSuccess(hasInterlocutor); 383 | } 384 | } 385 | 386 | void reportRejoinError(Integer errorCode) { 387 | if (mChatReconnectionListener != null) { 388 | mChatReconnectionListener.onRejoinError(Errors.getErrorByCode(errorCode)); 389 | } 390 | } 391 | 392 | void reportConnectionLost() { 393 | mFileQueue.clear(); 394 | if (mChatReconnectionListener != null) { 395 | mChatReconnectionListener.onConnectionLost(); 396 | } 397 | } 398 | 399 | /** 400 | * Sets chat reconnection listener to specified listener 401 | * 402 | * @param chatReconnectionListener chat reconnection listener 403 | */ 404 | public void setChatReconnectionListener(ChatReconnectionListener chatReconnectionListener) { 405 | mChatReconnectionListener = chatReconnectionListener; 406 | } 407 | 408 | /** 409 | * Checks if there is any interlocutor in chat 410 | * 411 | * @return has interlocutor 412 | */ 413 | public boolean hasInterlocutor() { 414 | return mHasInterlocutor; 415 | } 416 | 417 | /** 418 | * Sets if there is any interlocutor in chat 419 | * 420 | * @param hasInterlocutor has interlocutor 421 | */ 422 | void setHasInterlocutor(boolean hasInterlocutor) { 423 | this.mHasInterlocutor = hasInterlocutor; 424 | } 425 | 426 | private static class FileUploadData { 427 | final InputStream fileStream; 428 | final String mimeType; 429 | final FileUploadedCallback callback; 430 | 431 | FileUploadData(InputStream is, String mimeType, FileUploadedCallback cb) { 432 | this.fileStream = is; 433 | this.mimeType = mimeType; 434 | this.callback = cb; 435 | } 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/models/ChatRoomsManager.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.models; 2 | 3 | import com.metarhia.jstp.compiler.annotations.handlers.Array; 4 | import com.metarhia.metacom.connection.AndroidJSTPConnection; 5 | import com.metarhia.metacom.connection.Errors; 6 | import com.metarhia.metacom.connection.JSTPOkErrorHandler; 7 | import com.metarhia.metacom.interfaces.JoinRoomCallback; 8 | import com.metarhia.metacom.interfaces.LeaveRoomCallback; 9 | import com.metarhia.metacom.utils.Constants; 10 | import com.metarhia.metacom.utils.FileUtils; 11 | import com.metarhia.metacom.utils.HistoryCallback; 12 | import com.metarhia.metacom.utils.MainExecutor; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Manager for user connection chats 19 | * 20 | * @author lidaamber 21 | */ 22 | 23 | public class ChatRoomsManager implements AndroidJSTPConnection.AndroidJSTPConnectionListener { 24 | 25 | /** 26 | * List of user chat rooms 27 | */ 28 | private final List mChatRooms; 29 | 30 | /** 31 | * MetaCom connection 32 | */ 33 | private final AndroidJSTPConnection mConnection; 34 | 35 | /** 36 | * Creates new chat rooms manager 37 | */ 38 | ChatRoomsManager(AndroidJSTPConnection connection) { 39 | mConnection = connection; 40 | mChatRooms = new ArrayList<>(); 41 | 42 | mConnection.addListener(this); 43 | } 44 | 45 | /** 46 | * Adds new chat room by name 47 | * 48 | * @param roomName room name to be added 49 | * @param callback callback after attempt to create chat (success and error) 50 | */ 51 | public void addChatRoom(final String roomName, final JoinRoomCallback callback) { 52 | joinRoom(roomName, new JSTPOkErrorHandler(MainExecutor.get()) { 53 | @Override 54 | public void onOk(List args) { 55 | 56 | Boolean hasInterlocutor = (Boolean) args.get(0); 57 | 58 | ChatRoom room = new ChatRoom(roomName, mConnection); 59 | room.setHasInterlocutor(hasInterlocutor); 60 | 61 | mChatRooms.add(room); 62 | callback.onJoinedRoom(hasInterlocutor); 63 | } 64 | 65 | @Override 66 | public void onError(Integer errorCode) { 67 | callback.onJoinError(Errors.getErrorByCode(errorCode)); 68 | } 69 | }); 70 | 71 | } 72 | 73 | private void joinRoom(String roomName, JSTPOkErrorHandler handler) { 74 | List args = new ArrayList<>(); 75 | args.add(roomName); 76 | mConnection.cacheCall(Constants.META_COM, "join", args, handler); 77 | } 78 | 79 | /** 80 | * Gets chat room by name 81 | * 82 | * @param roomName chat name 83 | * @return required chat 84 | */ 85 | public ChatRoom getChatRoom(String roomName) { 86 | for (ChatRoom chatRoom : mChatRooms) { 87 | if (chatRoom.getChatRoomName().equals(roomName)) { 88 | return chatRoom; 89 | } 90 | } 91 | return null; 92 | } 93 | 94 | /** 95 | * Leaves chat room and removes char room from chats list 96 | * 97 | * @param chatRoom chatRoom to be removed 98 | */ 99 | public void leaveChatRoom(final ChatRoom chatRoom, final LeaveRoomCallback callback) { 100 | mConnection.cacheCall(Constants.META_COM, "leave", new ArrayList<>(), 101 | new JSTPOkErrorHandler(MainExecutor.get()) { 102 | @Override 103 | public void onOk(List args) { 104 | chatRoom.removeAllHandlers(); 105 | mChatRooms.remove(chatRoom); 106 | callback.onLeavedRoom(); 107 | } 108 | 109 | @Override 110 | public void onError(Integer errorCode) { 111 | callback.onLeaveError(Errors.getErrorByCode(errorCode)); 112 | } 113 | }); 114 | } 115 | 116 | @Override 117 | public void onConnectionEstablished(AndroidJSTPConnection connection) { 118 | if (mChatRooms.isEmpty()) return; 119 | for (final ChatRoom room : mChatRooms) { 120 | joinRoom(room.getChatRoomName(), new JSTPOkErrorHandler(MainExecutor.get()) { 121 | @Override 122 | public void onOk(List args) { 123 | Boolean hasInterlocutor = (Boolean) args.get(0); 124 | room.reportRejoinSuccess(hasInterlocutor); 125 | } 126 | 127 | @Override 128 | public void onError(@Array(0) Integer errorCode) { 129 | room.reportRejoinError(errorCode); 130 | } 131 | }); 132 | } 133 | 134 | } 135 | 136 | @Override 137 | public void onConnectionLost() { 138 | MainExecutor.get().execute(new Runnable() { 139 | @Override 140 | public void run() { 141 | if (mChatRooms.isEmpty()) return; 142 | 143 | for (ChatRoom room : mChatRooms) { 144 | room.reportConnectionLost(); 145 | } 146 | } 147 | }); 148 | } 149 | 150 | public void saveHistory(List messages, HistoryCallback callback) { 151 | StringBuilder messageBuilder = new StringBuilder(); 152 | for (Message message : messages) { 153 | if (message.getType() == MessageType.INFO) { 154 | messageBuilder.append(message.getContent() + "\n"); 155 | continue; 156 | } 157 | 158 | String sender = message.isIncoming() ? "Interlocutor: " : "Me: "; 159 | messageBuilder.append(sender); 160 | if (message.getType() == MessageType.FILE) { 161 | messageBuilder.append("File"); 162 | } else { 163 | messageBuilder.append(message.getContent()); 164 | } 165 | messageBuilder.append("\n"); 166 | } 167 | 168 | FileUtils.saveMessageHistory(messageBuilder.toString(), callback); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/models/ConnectionInfoProvider.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.models; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.metarhia.metacom.utils.FileUtils; 7 | 8 | import java.io.File; 9 | import java.util.Map; 10 | 11 | /** 12 | * Simple connection info provider that saves and restores user connections for more comfortable 13 | * usage 14 | * 15 | * @author lidaamber 16 | */ 17 | 18 | public class ConnectionInfoProvider { 19 | 20 | private static final String CONNECTION_FILENAME = "connectionInfo"; 21 | 22 | public static void saveConnectionInfo(Context context, String host, int port) { 23 | File file = new File(context.getFilesDir(), CONNECTION_FILENAME); 24 | 25 | FileUtils.saveConnectionInfo(file, host, port); 26 | } 27 | 28 | @Nullable 29 | public static Map restoreConnectionInfo(Context context) { 30 | File file = new File(context.getFilesDir(), CONNECTION_FILENAME); 31 | if (!file.exists()) return null; 32 | else { 33 | try { 34 | return FileUtils.readConnectionListFromFile(file); 35 | } catch (Exception e) { 36 | return null; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/models/FilesManager.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.models; 2 | 3 | import android.util.Base64; 4 | 5 | import com.metarhia.jstp.compiler.annotations.handlers.Array; 6 | import com.metarhia.jstp.core.Handlers.ManualHandler; 7 | import com.metarhia.jstp.core.JSInterfaces.JSObject; 8 | import com.metarhia.jstp.handlers.ExecutableHandler; 9 | import com.metarhia.metacom.connection.AndroidJSTPConnection; 10 | import com.metarhia.metacom.connection.Errors; 11 | import com.metarhia.metacom.connection.JSTPOkErrorHandler; 12 | import com.metarhia.metacom.interfaces.FileDownloadedListener; 13 | import com.metarhia.metacom.interfaces.FileUploadedCallback; 14 | import com.metarhia.metacom.utils.Constants; 15 | import com.metarhia.metacom.utils.FileUtils; 16 | import com.metarhia.metacom.utils.MainExecutor; 17 | 18 | import java.io.InputStream; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Manager for uploading and downloading files 24 | * 25 | * @author lidaamber 26 | */ 27 | 28 | public class FilesManager { 29 | 30 | /** 31 | * MetaCom connection 32 | */ 33 | private final AndroidJSTPConnection mConnection; 34 | 35 | /** 36 | * Current downloaded file chunks 37 | */ 38 | private ArrayList mCurrentFileBuffer; 39 | 40 | /** 41 | * Current downloaded file extension 42 | */ 43 | private String mCurrentExtension; 44 | 45 | /** 46 | * Current downloading file callback 47 | */ 48 | private FileDownloadedListener mCurrentCallback; 49 | 50 | /** 51 | * Creates new files manager 52 | */ 53 | FilesManager(AndroidJSTPConnection connection) { 54 | mConnection = connection; 55 | 56 | initTransferListener(); 57 | } 58 | 59 | /** 60 | * Uploads file to server specified in UserConnection 61 | * 62 | * @param fileStream file to upload 63 | * @param callback callback after file upload (success and error) 64 | */ 65 | public void uploadFile(InputStream fileStream, final FileUploadedCallback callback) { 66 | FileUtils.uploadSplitFile(fileStream, new FileUtils.FileUploadingInterface() { 67 | @Override 68 | public void sendChunk(byte[] chunk, JSTPOkErrorHandler handler) { 69 | FilesManager.this.sendChunk(chunk, handler); 70 | } 71 | 72 | @Override 73 | public void endFileUpload(FileUploadedCallback callback) { 74 | FilesManager.this.endFileUpload(callback); 75 | } 76 | }, callback); 77 | } 78 | 79 | /** 80 | * Downloads file from server specified in UserConnection 81 | * 82 | * @param fileCode code of file to download 83 | * @param callback callback after file download (success and error) 84 | */ 85 | public void downloadFile(String fileCode, final FileDownloadedListener callback) { 86 | List args = new ArrayList<>(); 87 | args.add(fileCode); 88 | mConnection.cacheCall(Constants.META_COM, "downloadFile", args, 89 | new JSTPOkErrorHandler(MainExecutor.get()) { 90 | @Override 91 | public void onOk(List args) { 92 | mCurrentCallback = callback; 93 | } 94 | 95 | @Override 96 | public void onError(@Array(0) Integer errorCode) { 97 | callback.onFileDownloadError(); 98 | } 99 | }); 100 | } 101 | 102 | private void initTransferListener() { 103 | mConnection.addEventHandler(Constants.META_COM, "downloadFileStart", 104 | new ManualHandler() { 105 | @Override 106 | public void handle(JSObject jsObject) { 107 | List messagePayload = (List) (jsObject).get("downloadFileStart"); 108 | String type = (String) messagePayload.get(0); 109 | mCurrentExtension = (type == null) ? "txt" : 110 | FileUtils.sMimeTypeMap.getExtensionFromMimeType(type); 111 | 112 | mCurrentFileBuffer = new ArrayList<>(); 113 | } 114 | }); 115 | 116 | mConnection.addEventHandler(Constants.META_COM, "downloadFileChunk", 117 | new ManualHandler() { 118 | @Override 119 | public void handle(JSObject jsObject) { 120 | List messagePayload = (List) (jsObject).get("downloadFileChunk"); 121 | String fileChunk = (String) messagePayload.get(0); 122 | if (mCurrentFileBuffer != null) 123 | mCurrentFileBuffer.add(Base64.decode(fileChunk, Base64.NO_WRAP)); 124 | } 125 | }); 126 | 127 | mConnection.addEventHandler(Constants.META_COM, "downloadFileEnd", 128 | new ExecutableHandler(MainExecutor.get()) { 129 | @Override 130 | public void run() { 131 | FileUtils.saveFileInDownloads(mCurrentExtension, mCurrentFileBuffer, 132 | mCurrentCallback); 133 | } 134 | }); 135 | } 136 | 137 | /** 138 | * Sends chunk to server 139 | * 140 | * @param chunk chunk to send 141 | * @param handler JSTP handler 142 | */ 143 | private void sendChunk(byte[] chunk, JSTPOkErrorHandler handler) { 144 | List args = new ArrayList<>(); 145 | args.add(Base64.encodeToString(chunk, Base64.NO_WRAP)); 146 | 147 | mConnection.cacheCall(Constants.META_COM, "uploadFileChunk", args, handler); 148 | } 149 | 150 | /** 151 | * Ends file upload to server 152 | * 153 | * @param callback callback after ending file upload 154 | */ 155 | private void endFileUpload(final FileUploadedCallback callback) { 156 | mConnection.cacheCall(Constants.META_COM, "endFileUpload", new ArrayList<>(), 157 | new JSTPOkErrorHandler(MainExecutor.get()) { 158 | @Override 159 | public void onOk(List args) { 160 | String fileCode = (String) args.get(0); 161 | callback.onFileUploaded(fileCode); 162 | } 163 | 164 | @Override 165 | public void onError(Integer errorCode) { 166 | callback.onFileUploadError(Errors.getErrorByCode(errorCode)); 167 | } 168 | }); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/models/Message.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.models; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Message structure 7 | * 8 | * @author lidaamber 9 | */ 10 | 11 | public class Message implements Serializable { 12 | 13 | /** 14 | * Type of the message 15 | */ 16 | private final MessageType mType; 17 | 18 | /** 19 | * Message content 20 | */ 21 | private final String mContent; 22 | 23 | /** 24 | * Shows if message belongs to user or it's incoming 25 | */ 26 | private final boolean mIsIncoming; 27 | 28 | /** 29 | * Shows if message is sending or receiving now 30 | */ 31 | private boolean mIsWaiting; 32 | 33 | /** 34 | * Creates message with specified type and content 35 | * 36 | * @param type message type 37 | * @param content message content 38 | */ 39 | public Message(MessageType type, String content, boolean isIncoming) { 40 | mType = type; 41 | mContent = content; 42 | mIsIncoming = isIncoming; 43 | } 44 | 45 | /** 46 | * Gets message type 47 | * 48 | * @return message type 49 | */ 50 | public MessageType getType() { 51 | return mType; 52 | } 53 | 54 | /** 55 | * Gets message content 56 | * 57 | * @return message content 58 | */ 59 | public String getContent() { 60 | return mContent; 61 | } 62 | 63 | public boolean isIncoming() { 64 | return mIsIncoming; 65 | } 66 | 67 | public boolean isWaiting() { 68 | return mIsWaiting; 69 | } 70 | 71 | public void setWaiting(boolean isWaiting) { 72 | mIsWaiting = isWaiting; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/models/MessageType.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.models; 2 | 3 | /** 4 | * Type of message 5 | * 6 | * @author lidaamber 7 | */ 8 | 9 | public enum MessageType { 10 | 11 | FILE, 12 | TEXT, 13 | INFO 14 | } 15 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/models/UserConnection.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.models; 2 | 3 | import com.metarhia.metacom.connection.AndroidJSTPConnection; 4 | 5 | /** 6 | * Connection to certain server mHost and mPort for chat and file exchange purposes 7 | * 8 | * @author lidaamber 9 | */ 10 | 11 | public class UserConnection { 12 | 13 | /** 14 | * Identifier of connection 15 | */ 16 | private final int mId; 17 | 18 | /** 19 | * Chats manager 20 | */ 21 | private final ChatRoomsManager mChatRoomsManager; 22 | 23 | /** 24 | * Files manager 25 | */ 26 | private final FilesManager mFilesManager; 27 | private AndroidJSTPConnection mConnection; 28 | 29 | /** 30 | * Creates new user connection 31 | */ 32 | UserConnection(int id, AndroidJSTPConnection connection) { 33 | mId = id; 34 | 35 | mChatRoomsManager = new ChatRoomsManager(connection); 36 | mFilesManager = new FilesManager(connection); 37 | mConnection = connection; 38 | } 39 | 40 | /** 41 | * Gets connection ID 42 | * 43 | * @return connection ID 44 | */ 45 | public int getId() { 46 | return mId; 47 | } 48 | 49 | /** 50 | * Gets connection chats manager 51 | * 52 | * @return connection chats manager 53 | */ 54 | public ChatRoomsManager getChatRoomsManager() { 55 | return mChatRoomsManager; 56 | } 57 | 58 | /** 59 | * Gets connection files manager 60 | * 61 | * @return connection files manager 62 | */ 63 | public FilesManager getFilesManager() { 64 | return mFilesManager; 65 | } 66 | 67 | public void closeConnection() { 68 | mConnection.close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/models/UserConnectionsManager.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.models; 2 | 3 | import android.content.Context; 4 | 5 | import com.metarhia.metacom.connection.AndroidJSTPConnection; 6 | import com.metarhia.metacom.interfaces.ConnectionCallback; 7 | import com.metarhia.metacom.utils.Constants; 8 | import com.metarhia.metacom.utils.MainExecutor; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Manager for user connections to various server hosts and ports 15 | * 16 | * @author lidaamber 17 | */ 18 | 19 | public class UserConnectionsManager { 20 | 21 | /** 22 | * User connections manager instance 23 | */ 24 | private static UserConnectionsManager instance; 25 | 26 | /** 27 | * List of user connections 28 | */ 29 | private final List mUserConnections; 30 | 31 | /** 32 | * Gets user connection manager instance 33 | * 34 | * @return user connection manager instance 35 | */ 36 | public static UserConnectionsManager get() { 37 | if (instance == null) { 38 | instance = new UserConnectionsManager(); 39 | } 40 | 41 | return instance; 42 | } 43 | 44 | /** 45 | * Creates new user connections manager 46 | */ 47 | private UserConnectionsManager() { 48 | mUserConnections = new ArrayList<>(); 49 | } 50 | 51 | /** 52 | * Adds new connection with required server host and port 53 | * 54 | * @param context application context 55 | * @param host required server host 56 | * @param port required server port 57 | * @param cb callback after attempt to create connection (success and error) 58 | */ 59 | public void addConnection(Context context, String host, int port, final ConnectionCallback cb) { 60 | final AndroidJSTPConnection connection = 61 | new AndroidJSTPConnection(host, port, true, context); 62 | connection.addListener(new AndroidJSTPConnection.AndroidJSTPConnectionListener() { 63 | @Override 64 | public void onConnectionEstablished(final AndroidJSTPConnection connection) { 65 | final AndroidJSTPConnection.AndroidJSTPConnectionListener listener = this; 66 | MainExecutor.get().execute(new Runnable() { 67 | @Override 68 | public void run() { 69 | UserConnection uc = new UserConnection(mUserConnections.size(), connection); 70 | mUserConnections.add(uc); 71 | cb.onConnectionEstablished(uc.getId()); 72 | connection.removeListener(listener); 73 | } 74 | }); 75 | } 76 | 77 | @Override 78 | public void onConnectionLost() { 79 | final AndroidJSTPConnection.AndroidJSTPConnectionListener listener = this; 80 | MainExecutor.get().execute(new Runnable() { 81 | @Override 82 | public void run() { 83 | cb.onConnectionError(); 84 | connection.removeListener(listener); 85 | } 86 | }); 87 | } 88 | }); 89 | connection.openConnection(Constants.APPLICATION_NAME); 90 | } 91 | 92 | /** 93 | * Gets user connection by ID 94 | * 95 | * @param connectionID connection ID 96 | * @return user connection 97 | */ 98 | public UserConnection getConnection(int connectionID) { 99 | if (connectionID >= 0 && connectionID < mUserConnections.size()) { 100 | return mUserConnections.get(connectionID); 101 | } else { 102 | return null; 103 | } 104 | } 105 | 106 | /** 107 | * Removes connection from connection list 108 | * 109 | * @param connection connection to be removed 110 | */ 111 | public void removeConnection(UserConnection connection) { 112 | mUserConnections.remove(connection); 113 | connection.closeConnection(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.utils; 2 | 3 | import android.content.res.Resources; 4 | 5 | import com.metarhia.metacom.R; 6 | 7 | /** 8 | * Application constants 9 | * 10 | * @author lidaamber 11 | */ 12 | 13 | public class Constants { 14 | 15 | private static Resources sResources; 16 | 17 | public static void initResources(Resources resources) { 18 | sResources = resources; 19 | 20 | EVENT_CHAT_JOIN = sResources.getString(R.string.event_chat_join); 21 | EVENT_CHAT_LEAVE = sResources.getString(R.string.event_chat_leave); 22 | DOWNLOAD_FAILED = sResources.getString(R.string.download_failed); 23 | } 24 | 25 | public static final String APPLICATION_NAME = "metarhia.com"; 26 | 27 | public static final String META_COM = "metacom"; 28 | 29 | public static final String ACTION_NEEDS_CONNECTION = "actionNeedsConnection"; 30 | public static final String ACTION_HAS_CONNECTION = "actionHasConnection"; 31 | 32 | public static String EVENT_CHAT_JOIN; 33 | public static String EVENT_CHAT_LEAVE; 34 | public static String DOWNLOAD_FAILED; 35 | 36 | 37 | /** 38 | * Creates info message about file path 39 | * 40 | * @param filePath file path 41 | * @return info message about file path 42 | */ 43 | public static String composeFilePathInfo(String filePath) { 44 | return String.format(sResources.getString(R.string.file_path), filePath); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.utils; 2 | 3 | import android.os.Environment; 4 | import android.os.Handler; 5 | import android.os.HandlerThread; 6 | import android.webkit.MimeTypeMap; 7 | 8 | import com.metarhia.metacom.connection.Errors; 9 | import com.metarhia.metacom.connection.JSTPOkErrorHandler; 10 | import com.metarhia.metacom.interfaces.FileDownloadedListener; 11 | import com.metarhia.metacom.interfaces.FileUploadedCallback; 12 | 13 | import java.io.File; 14 | import java.io.FileInputStream; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.ObjectInputStream; 19 | import java.io.ObjectOutputStream; 20 | import java.io.OutputStream; 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | /** 28 | * Utils for files manipulations 29 | * 30 | * @author lidaamber 31 | */ 32 | 33 | public class FileUtils { 34 | 35 | private static final String FILE_HANDLER_THREAD = "fileHandlerThread"; 36 | 37 | public static MimeTypeMap sMimeTypeMap = MimeTypeMap.getSingleton(); 38 | 39 | static { 40 | HandlerThread fileHandlerThread = new HandlerThread(FILE_HANDLER_THREAD); 41 | fileHandlerThread.start(); 42 | sFileHandler = new Handler(fileHandlerThread.getLooper()); 43 | } 44 | 45 | /** 46 | * Handler processing files manipulations 47 | */ 48 | private static Handler sFileHandler; 49 | 50 | /** 51 | * Size of chunk to split file 52 | */ 53 | private static final int FILE_CHUNK_SIZE = 1024 * 1024; 54 | 55 | /** 56 | * Uploads file to server 57 | * 58 | * @param is file to upload 59 | * @param sendInterface send and end sending methods specific for uploading 60 | * @param callback callback after file upload (success and error) 61 | */ 62 | public static void uploadSplitFile(InputStream is, final FileUploadingInterface sendInterface, 63 | final FileUploadedCallback callback) { 64 | splitFile(is, FILE_CHUNK_SIZE, new FileUtils.FileContentsCallback() { 65 | @Override 66 | public void onSplitToChunks(List chunks) { 67 | final Iterator chunkIterator = chunks.iterator(); 68 | if (chunkIterator.hasNext()) { 69 | byte[] chunk = chunkIterator.next(); 70 | final JSTPOkErrorHandler handler = new JSTPOkErrorHandler(MainExecutor.get()) { 71 | @Override 72 | public void onOk(List args) { 73 | if (chunkIterator.hasNext()) { 74 | sendInterface.sendChunk(chunkIterator.next(), this); 75 | } else { 76 | sendInterface.endFileUpload(callback); 77 | } 78 | } 79 | 80 | @Override 81 | public void onError(Integer errorCode) { 82 | callback.onFileUploadError(Errors.getErrorByCode(errorCode)); 83 | } 84 | }; 85 | sendInterface.sendChunk(chunk, handler); 86 | } 87 | } 88 | 89 | @Override 90 | public void onSplitError(Exception e) { 91 | callback.onFileUploadError(Errors.getErrorByCode(Errors.ERR_FILE_LOAD)); 92 | } 93 | }); 94 | } 95 | 96 | /** 97 | * Splits file into byte[] chunks 98 | * 99 | * @param fileStream file to split 100 | * @param chunkSize size of the chunk 101 | * @param callback callback on file split 102 | */ 103 | private static void splitFile(final InputStream fileStream, final int chunkSize, 104 | final FileContentsCallback callback) { 105 | sFileHandler.post(new Runnable() { 106 | @Override 107 | public void run() { 108 | List chunks = new ArrayList<>(); 109 | try { 110 | int available = fileStream.available(); 111 | int currentBufferSize = available < chunkSize ? available : chunkSize; 112 | byte[] buf = new byte[available < chunkSize ? available : chunkSize]; 113 | 114 | while (fileStream.read(buf, 0, currentBufferSize) != -1) { 115 | chunks.add(buf); 116 | available -= buf.length; 117 | currentBufferSize = available < chunkSize ? available : chunkSize; 118 | if (currentBufferSize <= 0) break; 119 | buf = new byte[currentBufferSize]; 120 | } 121 | fileStream.close(); 122 | 123 | callback.onSplitToChunks(chunks); 124 | } catch (Exception e) { 125 | callback.onSplitError(e); 126 | } 127 | } 128 | }); 129 | } 130 | 131 | /** 132 | * Gets downloads storage 133 | */ 134 | public static void saveFileInDownloads(String extension, ArrayList buffer, 135 | final FileDownloadedListener callback) { 136 | try { 137 | File path = (Environment.getExternalStorageState() == null || 138 | !Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) ? 139 | Environment.getDataDirectory() : 140 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 141 | 142 | final File file = new File(path, System.currentTimeMillis() + "." + extension); 143 | if (file.createNewFile()) { 144 | 145 | FileUtils.writeChunksToFile(file, buffer, 146 | new FileUtils.FileWritingCallback() { 147 | @Override 148 | public void onWrittenToFile() { 149 | MainExecutor.get().execute(new Runnable() { 150 | @Override 151 | public void run() { 152 | callback.onFileDownloaded(file.getAbsolutePath()); 153 | } 154 | }); 155 | } 156 | 157 | @Override 158 | public void onWriteError(Exception e) { 159 | callback.onFileDownloadError(); 160 | } 161 | }); 162 | } 163 | 164 | } catch (IOException e) { 165 | callback.onFileDownloadError(); 166 | } 167 | } 168 | 169 | /** 170 | * Writes file chunks to file 171 | * 172 | * @param file file to be written 173 | * @param chunks file chunks 174 | */ 175 | private static void writeChunksToFile(final File file, final ArrayList chunks, 176 | final FileWritingCallback callback) { 177 | sFileHandler.post(new Runnable() { 178 | @Override 179 | public void run() { 180 | FileOutputStream stream; 181 | try { 182 | stream = new FileOutputStream(file); 183 | for (byte[] chunk : chunks) { 184 | stream.write(chunk); 185 | } 186 | stream.flush(); 187 | stream.close(); 188 | 189 | callback.onWrittenToFile(); 190 | 191 | } catch (Exception e) { 192 | callback.onWriteError(e); 193 | } 194 | } 195 | }); 196 | } 197 | 198 | public static void saveConnectionInfo(File file, String host, int port) { 199 | try { 200 | if (!file.exists()) { 201 | file.createNewFile(); 202 | Map infoList = new HashMap<>(); 203 | infoList.put(host, port); 204 | 205 | writeConnectionListToFile(infoList, file); 206 | } else { 207 | Map infoList = readConnectionListFromFile(file); 208 | 209 | if (infoList != null) { 210 | infoList.put(host, port); 211 | } 212 | 213 | writeConnectionListToFile(infoList, file); 214 | } 215 | } catch (IOException ignored) { 216 | } catch (ClassNotFoundException ignored) { 217 | } 218 | } 219 | 220 | public static Map readConnectionListFromFile(File file) throws IOException, 221 | ClassNotFoundException { 222 | ObjectInputStream is = new ObjectInputStream(new FileInputStream(file)); 223 | Map infoList = (Map) is.readObject(); 224 | is.close(); 225 | 226 | return infoList; 227 | } 228 | 229 | private static void writeConnectionListToFile(Map infoList, File file) throws 230 | IOException { 231 | ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file)); 232 | 233 | os.writeObject(infoList); 234 | os.flush(); 235 | os.close(); 236 | } 237 | 238 | public static void saveMessageHistory(String s, HistoryCallback callback) { 239 | try { 240 | String filename = System.currentTimeMillis() + ".txt"; 241 | 242 | File path = (Environment.getExternalStorageState() == null || 243 | !Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) ? 244 | Environment.getDataDirectory() : 245 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 246 | final File file = new File(path, filename); 247 | if (file.mkdirs() && file.createNewFile()) { 248 | OutputStream os = new FileOutputStream(file); 249 | os.write(s.getBytes()); 250 | 251 | os.flush(); 252 | os.close(); 253 | callback.onHistorySaved(filename); 254 | } 255 | 256 | } catch (IOException e) { 257 | callback.onSaveError(); 258 | } 259 | } 260 | 261 | /** 262 | * Callback for splitting files 263 | */ 264 | private interface FileContentsCallback { 265 | /** 266 | * Called when file is split successfully 267 | * 268 | * @param chunks file chunks 269 | */ 270 | void onSplitToChunks(List chunks); 271 | 272 | /** 273 | * Called when splitting fails 274 | * 275 | * @param e exception thrown while splitting 276 | */ 277 | void onSplitError(Exception e); 278 | } 279 | 280 | /** 281 | * Callback for writing into file 282 | */ 283 | private interface FileWritingCallback { 284 | 285 | /** 286 | * Called when content was written successfully 287 | */ 288 | void onWrittenToFile(); 289 | 290 | /** 291 | * Called when writing failed 292 | * 293 | * @param e exception thrown while writing 294 | */ 295 | void onWriteError(Exception e); 296 | } 297 | 298 | /** 299 | * Interface used to describe file uploading 300 | */ 301 | public interface FileUploadingInterface { 302 | void sendChunk(byte[] chunk, JSTPOkErrorHandler handler); 303 | 304 | void endFileUpload(FileUploadedCallback callback); 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/utils/HistoryCallback.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.utils; 2 | 3 | /** 4 | * @author lidaamber 5 | */ 6 | 7 | public interface HistoryCallback { 8 | void onHistorySaved(String filename); 9 | 10 | void onSaveError(); 11 | } 12 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/utils/KeyboardUtils.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.view.View; 6 | import android.view.inputmethod.InputMethodManager; 7 | 8 | /** 9 | * Created by masha on 9/15/17. 10 | */ 11 | 12 | public class KeyboardUtils { 13 | 14 | public static void hideKeyboard(Activity activity) { 15 | View currentFocus = activity.getCurrentFocus(); 16 | if (currentFocus != null) { 17 | InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context 18 | .INPUT_METHOD_SERVICE); 19 | imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/utils/MainExecutor.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.utils; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.support.annotation.NonNull; 6 | 7 | import java.util.concurrent.Executor; 8 | 9 | /** 10 | * Main thread executor 11 | * 12 | * @author lidaamber 13 | */ 14 | 15 | public class MainExecutor implements Executor { 16 | 17 | /** 18 | * Executor instance 19 | */ 20 | private static MainExecutor sInstance; 21 | 22 | /** 23 | * Main thread handler 24 | */ 25 | private final Handler handler = new Handler(Looper.getMainLooper()); 26 | 27 | /** 28 | * Creates new main thread executor 29 | */ 30 | private MainExecutor() { 31 | } 32 | 33 | /** 34 | * Gets executor instance 35 | * 36 | * @return main thread executor 37 | */ 38 | public static MainExecutor get() { 39 | if (sInstance == null) { 40 | sInstance = new MainExecutor(); 41 | } 42 | 43 | return sInstance; 44 | } 45 | 46 | @Override 47 | public void execute(@NonNull Runnable runnable) { 48 | handler.post(runnable); 49 | } 50 | } -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/utils/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | /** 8 | * Check device's network NetworkUtils and speed 9 | * 10 | * @author emil http://stackoverflow.com/users/220710/emil 11 | * minor changes lundibundi 12 | */ 13 | public class NetworkUtils { 14 | 15 | private static NetworkInfo getNetworkInfo(Context context) { 16 | ConnectivityManager cm = (ConnectivityManager) 17 | context.getSystemService(Context.CONNECTIVITY_SERVICE); 18 | return cm.getActiveNetworkInfo(); 19 | } 20 | 21 | public static boolean isConnected(Context context) { 22 | NetworkInfo info = NetworkUtils.getNetworkInfo(context); 23 | return (info != null && info.isConnected()); 24 | } 25 | 26 | public static boolean isConnectedWifi(Context context) { 27 | NetworkInfo info = NetworkUtils.getNetworkInfo(context); 28 | return (info != null && info.isConnected() && 29 | info.getType() == ConnectivityManager.TYPE_WIFI); 30 | } 31 | 32 | public static boolean isConnectedFast(Context context) { 33 | return isConnected(context); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/utils/PermissionUtils.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.utils; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.content.ContextCompat; 8 | 9 | /** 10 | * @author MariaKokshaikina 11 | */ 12 | 13 | public class PermissionUtils { 14 | 15 | public static final int ANDROID_VERSION = Build.VERSION.SDK_INT; 16 | public static final int REQUEST_CODE = 1; 17 | 18 | public static boolean checkVersion() { 19 | return ANDROID_VERSION > Build.VERSION_CODES.LOLLIPOP_MR1; 20 | } 21 | 22 | public static boolean checkIfAlreadyHavePermission(Context context) { 23 | int write = ContextCompat.checkSelfPermission(context, 24 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE); 25 | int read = ContextCompat.checkSelfPermission(context, 26 | android.Manifest.permission.READ_EXTERNAL_STORAGE); 27 | return write == PackageManager.PERMISSION_GRANTED && 28 | read == PackageManager.PERMISSION_GRANTED; 29 | } 30 | 31 | public static void requestForStoragePermission(Fragment fragment) { 32 | fragment.requestPermissions(new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE, 33 | android.Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/java/com/metarhia/metacom/utils/TextUtils.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.ClipData; 5 | import android.content.ClipboardManager; 6 | import android.content.Context; 7 | 8 | /** 9 | * Created by masha on 7/31/17. 10 | */ 11 | 12 | public class TextUtils { 13 | 14 | public static void copyToClipboard(Activity activity, String text) { 15 | ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context 16 | .CLIPBOARD_SERVICE); 17 | ClipData clip = ClipData.newPlainText("copy", text); 18 | clipboard.setPrimaryClip(clip); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_arrow_back_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_attach_file_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_attach_file_grey_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_error_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_file_download_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_file_download_grey_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_file_upload_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_file_upload_grey_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_insert_drive_file_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/ic_mail_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/message_in.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 12 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/message_out.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/drawable/metarhia_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/drawable/metarhia_logo.png -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/activity_connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/bottom_notice.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/fragment_chat.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 20 | 21 | 30 | 31 | 42 | 43 | 61 | 62 | 63 | 64 | 71 | 72 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/fragment_chat_login.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 30 | 31 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/fragment_connection.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 14 | 19 | 20 | 29 | 30 | 43 | 44 | 55 | 56 | 67 | 68 | 76 | 77 | 78 | 89 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/fragment_download_code_dialog.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/fragment_files.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 21 | 22 | 28 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 15 | 16 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/fragment_upload_file_dialog.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 19 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/message_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/message_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 19 | 32 | 33 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/message_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/message_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 33 | 34 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @color/orange 6 | @color/black6 7 | @color/white 8 | 9 | #000000 10 | #060606 11 | #1c1c1c 12 | 13 | #FFFFFF 14 | 15 | #1d2217 16 | #888888 17 | 18 | #df9034 19 | #e8b889 20 | 21 | 22 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16sp 4 | 12sp 5 | 6 | 5dp 7 | 10dp 8 | 20dp 9 | 10 | 20dp 11 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MetaCom 3 | HOST_LAST_USED 4 | PORT_LAST_USED 5 | IS_AUTHORIZED 6 | 7 | 8 | Host 9 | Port 10 | Connect 11 | Could\'nt connect to server 12 | 13 | Open via 14 | Server Installation Guide 15 | https://github.com/metarhia/server/blob/master/README.md 16 | 17 | 18 | %1$s:%2$s 19 | Files 20 | Chat 21 | 22 | 23 | Enter download code: 24 | File code 25 | Downloading… 26 | Uploading… 27 | Complete! Click to open file 28 | Take a photo 29 | Open file explorer 30 | File download with current code failed 31 | File upload failed 32 | Select file… 33 | Open file… 34 | Code was copied 35 | 36 | 37 | Join 38 | Chat name 39 | 40 | 41 | New Message 42 | File was sent 43 | Message was copied 44 | Connection lost 45 | There is someone in the chat 46 | Message 47 | Copy 48 | Resend 49 | Send 50 | Connection established 51 | 52 | 53 | Cancel 54 | Download 55 | OK 56 | 57 | 58 | Leave chat 59 | Do you want to leave chat? 60 | 61 | Leave server 62 | Leaving the server will cause aborting all file transitions 63 | 64 | Upload 65 | Your file was uploaded. The code is %s 66 | 67 | 68 | An error occurred while joining chat room. The room is already taken 69 | You are not in chat 70 | There is no one in the chat 71 | No such file 72 | Upload not started 73 | Previous upload not finished 74 | 75 | 76 | Could\'nt load file 77 | 78 | 79 | Somebody has joined the chat 80 | Somebody has left the chat 81 | Your file is saved here: %s 82 | Download failed 83 | 84 | 85 | Permissions 86 | Please, confirm the storage permission to make file transactions. 87 | You can\'t make file transactions without confirming permission 88 | Save messaging history 89 | Do you want to save messaging history? 90 | No 91 | Yes 92 | Saved history in %s in downloads directory 93 | Save history error 94 | 95 | 96 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /MetaCom/app/src/main/res/xml/paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /MetaCom/app/src/test/java/com/metarhia/metacom/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.metarhia.metacom; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /MetaCom/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /MetaCom/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /MetaCom/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metarhia/metacom-android/2888ae850fefa49dff503cd5118e1ee20fee4edb/MetaCom/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /MetaCom/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jan 08 23:15:30 EET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /MetaCom/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /MetaCom/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /MetaCom/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Metacommunicator for Android 2 | 3 | ### Now available in [Play Market](https://play.google.com/store/apps/details?id=com.metarhia.metacom) 🎉 4 | 5 | MetaCom is a very secure instant messenger and file exchange tool. Both messaging and file transactions are anonymous and private. 6 | 7 | You shouldn’t doubt about security and reliability as all your messaging and file exchange transactions run on your own server. You can find all the instructions about server installation [here](https://github.com/metarhia/server/blob/master/README.md). 8 | 9 | You can exchange any files without limits of size. Once you upload the file, you receive its code. You can download the file by code from server and it gets deleted from server right after being downloaded. 10 | 11 | Chatting is very secure as well: to chat with someone, you create a room with specific name. One does not simply enter your chat room and see your conversation as there can only be two interlocutors in the room. All the files you transfer in chat are sent right to your interlocutor and are not ever stored on server. Once you leave the chat room, all messaging history is deleted. 12 | --------------------------------------------------------------------------------