diff --git a/app/build.gradle b/app/build.gradle index 0e713036b..7efa15c55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -172,6 +172,7 @@ configurations.all { dependencies { implementation fileTree(include: ['*'], dir: 'libs') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // Koin for Android implementation "org.koin:koin-android:$koin_version" // Koin AndroidX Scope features @@ -264,11 +265,6 @@ dependencies { implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.10.0.pr1' kapt 'com.bluelinelabs:logansquare-compiler:1.3.7' - implementation 'com.google.dagger:dagger:2.24' - kapt 'com.google.dagger:dagger-compiler:2.24' - kapt 'com.google.dagger:dagger-android-processor:2.24' - implementation 'com.github.lukaspili.autodagger2:autodagger2:1.1' - kapt 'com.github.lukaspili.autodagger2:autodagger2-compiler:1.1' compileOnly 'javax.annotation:jsr250-api:1.0' // Android only implementation 'org.greenrobot:eventbus:3.1.1' diff --git a/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json b/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json index 5bc9f32b0..911f82088 100644 --- a/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json +++ b/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "3cabfd9fea2cd5a25c6c1c8103ef65fb", + "identityHash": "95ddaea30271abd3160e1cdc8ab404d5", "entities": [ { "tableName": "conversations", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user` INTEGER, `conversation_id` TEXT, `token` TEXT, `name` TEXT, `display_name` TEXT, `type` INTEGER, `count` INTEGER NOT NULL, `number_of_guests` INTEGER NOT NULL, `participants_count` INTEGER NOT NULL, `participant_type` INTEGER, `has_password` INTEGER NOT NULL, `session_id` TEXT, `favorite` INTEGER NOT NULL, `last_activity` INTEGER NOT NULL, `unread_messages` INTEGER NOT NULL, `unread_mention` INTEGER NOT NULL, `last_message` TEXT, `object_type` TEXT, `notification_level` INTEGER, `read_only_state` INTEGER, `lobby_state` INTEGER, `lobby_timer` INTEGER, `last_read_message` INTEGER NOT NULL, `modified_at` INTEGER, `changing` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`user`) REFERENCES `users`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` INTEGER, `conversation_id` TEXT, `token` TEXT, `name` TEXT, `display_name` TEXT, `type` INTEGER, `count` INTEGER NOT NULL, `number_of_guests` INTEGER NOT NULL, `participants_count` INTEGER NOT NULL, `participant_type` INTEGER, `has_password` INTEGER NOT NULL, `session_id` TEXT, `favorite` INTEGER NOT NULL, `last_activity` INTEGER NOT NULL, `unread_messages` INTEGER NOT NULL, `unread_mention` INTEGER NOT NULL, `last_message` TEXT, `object_type` TEXT, `notification_level` INTEGER, `read_only_state` INTEGER, `lobby_state` INTEGER, `lobby_timer` INTEGER, `last_read_message` INTEGER NOT NULL, `modified_at` INTEGER, `changing` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`user_id`) REFERENCES `users`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", "fields": [ { "fieldPath": "id", @@ -16,7 +16,7 @@ }, { "fieldPath": "user", - "columnName": "user", + "columnName": "user_id", "affinity": "INTEGER", "notNull": false }, @@ -173,13 +173,13 @@ }, "indices": [ { - "name": "index_conversations_user_conversation_id", + "name": "index_conversations_user_id_token", "unique": true, "columnNames": [ - "user", - "conversation_id" + "user_id", + "token" ], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_conversations_user_conversation_id` ON `${TABLE_NAME}` (`user`, `conversation_id`)" + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_conversations_user_id_token` ON `${TABLE_NAME}` (`user_id`, `token`)" } ], "foreignKeys": [ @@ -188,7 +188,7 @@ "onDelete": "CASCADE", "onUpdate": "CASCADE", "columns": [ - "user" + "user_id" ], "referencedColumns": [ "id" @@ -198,7 +198,7 @@ }, { "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `system_message_type` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`conversation`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `system_message_type` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", "fields": [ { "fieldPath": "id", @@ -208,7 +208,7 @@ }, { "fieldPath": "conversation", - "columnName": "conversation", + "columnName": "conversation_id", "affinity": "TEXT", "notNull": true }, @@ -261,23 +261,14 @@ ], "autoGenerate": false }, - "indices": [ - { - "name": "index_messages_conversation", - "unique": false, - "columnNames": [ - "conversation" - ], - "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation` ON `${TABLE_NAME}` (`conversation`)" - } - ], + "indices": [], "foreignKeys": [ { "table": "conversations", "onDelete": "CASCADE", "onUpdate": "CASCADE", "columns": [ - "conversation" + "conversation_id" ], "referencedColumns": [ "id" @@ -369,7 +360,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3cabfd9fea2cd5a25c6c1c8103ef65fb')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '95ddaea30271abd3160e1cdc8ab404d5')" ] } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/nextcloud/talk/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/nextcloud/talk/ExampleInstrumentedTest.java index 5514687d6..29bbca125 100644 --- a/app/src/androidTest/java/com/nextcloud/talk/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/com/nextcloud/talk/ExampleInstrumentedTest.java @@ -21,6 +21,7 @@ package com.nextcloud.talk; import android.content.Context; + import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.java b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.java deleted file mode 100644 index 12a3006a6..000000000 --- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.services.firebase; - -import android.annotation.SuppressLint; - -import autodagger.AutoInjector; -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.jobs.NotificationWorker; -import com.nextcloud.talk.jobs.PushRegistrationWorker; -import com.nextcloud.talk.utils.bundle.BundleKeys; - -import androidx.work.Data; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; -import com.nextcloud.talk.utils.preferences.AppPreferences; - -import javax.inject.Inject; - -@AutoInjector(NextcloudTalkApplication.class) -public class MagicFirebaseMessagingService extends FirebaseMessagingService { - @Inject - AppPreferences appPreferences; - - @Override - public void onNewToken(String token) { - super.onNewToken(token); - NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); - appPreferences.setPushToken(token); - OneTimeWorkRequest pushRegistrationWork = new OneTimeWorkRequest.Builder(PushRegistrationWorker.class).build(); - WorkManager.getInstance().enqueue(pushRegistrationWork); - } - - @SuppressLint("LongLogTag") - @Override - public void onMessageReceived(RemoteMessage remoteMessage) { - if (remoteMessage == null) { - return; - } - - if (remoteMessage.getData() != null) { - Data messageData = new Data.Builder() - .putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SUBJECT(), remoteMessage.getData().get("subject")) - .putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SIGNATURE(), remoteMessage.getData().get("signature")) - .build(); - - OneTimeWorkRequest pushNotificationWork = new OneTimeWorkRequest.Builder(NotificationWorker.class) - .setInputData(messageData) - .build(); - WorkManager.getInstance().enqueue(pushNotificationWork); - } - } -} diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt new file mode 100644 index 000000000..91e5968fa --- /dev/null +++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt @@ -0,0 +1,63 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.services.firebase + +import android.annotation.SuppressLint +import autodagger.AutoInjector +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.jobs.NotificationWorker +import com.nextcloud.talk.jobs.PushRegistrationWorker +import com.nextcloud.talk.utils.bundle.BundleKeys +import androidx.work.Data +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import com.nextcloud.talk.utils.preferences.AppPreferences + +class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent { + val appPreferences: AppPreferences by inject() + + @Override + fun onNewToken(token: String?) { + super.onNewToken(token) + appPreferences.setPushToken(token) + val pushRegistrationWork: OneTimeWorkRequest = Builder(PushRegistrationWorker::class.java).build() + WorkManager.getInstance().enqueue(pushRegistrationWork) + } + + @SuppressLint("LongLogTag") + @Override + fun onMessageReceived(remoteMessage: RemoteMessage?) { + if (remoteMessage == null) { + return + } + if (remoteMessage.getData() != null) { + val messageData: Data = Builder() + .putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SUBJECT(), remoteMessage.getData().get("subject")) + .putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SIGNATURE(), remoteMessage.getData().get("signature")) + .build() + val pushNotificationWork: OneTimeWorkRequest = Builder(NotificationWorker::class.java) + .setInputData(messageData) + .build() + WorkManager.getInstance().enqueue(pushNotificationWork) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt index 76865365b..4947a261c 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt @@ -28,7 +28,6 @@ import android.util.Log import android.view.WindowManager import android.webkit.SslErrorHandler import androidx.appcompat.app.AppCompatActivity -import autodagger.AutoInjector import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.events.CertificateEvent @@ -39,25 +38,18 @@ import com.yarolegovich.lovelydialog.LovelyStandardDialog import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import org.koin.android.ext.android.inject import java.security.cert.CertificateParsingException import java.security.cert.X509Certificate import java.text.DateFormat -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) open class BaseActivity : AppCompatActivity() { - @Inject - lateinit var eventBus: EventBus - - @Inject - lateinit var appPreferences: AppPreferences - - @Inject - lateinit var context: Context + val eventBus: EventBus by inject() + val appPreferences: AppPreferences by inject() + val context: Context by inject() override fun onCreate(savedInstanceState: Bundle?) { - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) super.onCreate(savedInstanceState) } diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index d15d87ea0..a6aa00b29 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -27,7 +27,6 @@ import android.os.Build import android.os.Bundle import android.view.ViewGroup import androidx.annotation.RequiresApi -import autodagger.AutoInjector import butterknife.BindView import butterknife.ButterKnife import com.bluelinelabs.conductor.Conductor @@ -37,7 +36,6 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler import com.google.android.material.appbar.MaterialToolbar import com.nextcloud.talk.R -import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.controllers.CallNotificationController import com.nextcloud.talk.controllers.LockedController import com.nextcloud.talk.controllers.ServerSelectionController @@ -47,16 +45,11 @@ import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListVi import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.SecurityUtils import com.nextcloud.talk.utils.bundle.BundleKeys -import com.nextcloud.talk.utils.database.user.UserUtils -import io.requery.Persistable import io.requery.android.sqlcipher.SqlCipherDatabaseSource -import io.requery.reactivex.ReactiveEntityStore import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.koin.android.ext.android.inject -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class MainActivity : BaseActivity(), ActionBarProvider { @BindView(R.id.toolbar) @@ -65,9 +58,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { lateinit var container: ViewGroup val usersRepository: UsersRepository by inject() - - @Inject - lateinit var sqlCipherDatabaseSource: SqlCipherDatabaseSource + val sqlCipherDatabaseSource: SqlCipherDatabaseSource by inject() private var router: Router? = null @@ -76,7 +67,6 @@ class MainActivity : BaseActivity(), ActionBarProvider { setContentView(R.layout.activity_main) - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) ButterKnife.bind(this) setSupportActionBar(toolbar) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.kt index 2b352ce20..bde729fb4 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.kt @@ -22,11 +22,7 @@ package com.nextcloud.talk.adapters.items import android.accounts.Account import android.view.View -import android.widget.ImageButton -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.RelativeLayout -import android.widget.TextView +import android.widget.* import androidx.emoji.widget.EmojiTextView import androidx.recyclerview.widget.RecyclerView.ViewHolder import butterknife.BindView @@ -35,8 +31,8 @@ import coil.api.load import coil.transform.CircleCropTransformation import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.participants.Participant +import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.utils.ApiUtils import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem @@ -52,7 +48,7 @@ class AdvancedUserItem( */ val model: Participant, - val entity: UserEntity?, + val entity: UserNgEntity?, val account: Account? ) : AbstractFlexibleItem(), IFilterable { override fun bindViewHolder( diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/UserItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/UserItem.kt index 9755857bc..26a160c6c 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/UserItem.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/UserItem.kt @@ -32,7 +32,6 @@ import coil.api.load import coil.transform.CircleCropTransformation import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.newarch.local.models.UserNgEntity diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt index 7276260c3..b7749e882 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt @@ -33,7 +33,6 @@ import android.view.View import android.widget.TextView import androidx.core.view.ViewCompat import androidx.emoji.widget.EmojiTextView -import autodagger.AutoInjector import butterknife.BindView import butterknife.ButterKnife import com.amulyakhare.textdrawable.TextDrawable @@ -48,9 +47,7 @@ import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.messages.MessageHolders import org.koin.core.KoinComponent import org.koin.core.inject -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders .IncomingTextMessageViewHolder(incomingView), KoinComponent { @@ -70,9 +67,7 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders @BindView(R.id.messageTime) var messageTimeView: TextView? = null - @JvmField - @Inject - var context: Context? = null + val context: Context by inject() val appPreferences: AppPreferences by inject() @@ -81,9 +76,6 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders this, itemView ) - NextcloudTalkApplication.sharedApplication!! - .componentApplication - .inject(this) } override fun onBind(message: ChatMessage) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java deleted file mode 100644 index b5cb8ed7a..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.adapters.messages; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.text.Spannable; -import android.text.SpannableString; -import android.util.TypedValue; -import android.view.View; -import android.widget.TextView; -import androidx.core.view.ViewCompat; -import androidx.emoji.widget.EmojiTextView; -import autodagger.AutoInjector; -import butterknife.BindView; -import butterknife.ButterKnife; -import com.google.android.flexbox.FlexboxLayout; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.models.json.chat.ChatMessage; -import com.nextcloud.talk.utils.DisplayUtils; -import com.nextcloud.talk.utils.TextMatchers; -import com.nextcloud.talk.utils.database.user.UserUtils; -import com.stfalcon.chatkit.messages.MessageHolders; -import java.util.HashMap; -import java.util.Map; -import javax.inject.Inject; - -@AutoInjector(NextcloudTalkApplication.class) -public class MagicOutcomingTextMessageViewHolder - extends MessageHolders.OutcomingTextMessageViewHolder { - @BindView(R.id.messageText) - EmojiTextView messageText; - - @BindView(R.id.messageTime) - TextView messageTimeView; - - @Inject - UserUtils userUtils; - - @Inject - Context context; - - private View itemView; - - public MagicOutcomingTextMessageViewHolder(View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - - this.itemView = itemView; - } - - @Override - public void onBind(ChatMessage message) { - super.onBind(message); - - HashMap> messageParameters = message.getMessageParameters(); - - Spannable messageString = new SpannableString(message.getText()); - - itemView.setSelected(false); - messageTimeView.setTextColor(context.getResources().getColor(R.color.white60)); - - FlexboxLayout.LayoutParams layoutParams = - (FlexboxLayout.LayoutParams) messageTimeView.getLayoutParams(); - layoutParams.setWrapBefore(false); - - float textSize = context.getResources().getDimension(R.dimen.chat_text_size); - - if (messageParameters != null && messageParameters.size() > 0) { - for (String key : messageParameters.keySet()) { - Map individualHashMap = message.getMessageParameters().get(key); - if (individualHashMap != null) { - if (individualHashMap.get("type").equals("user") || individualHashMap.get("type") - .equals("guest") || individualHashMap.get("type").equals("call")) { - messageString = - DisplayUtils.INSTANCE.searchAndReplaceWithMentionSpan(messageText.getContext(), - messageString, - individualHashMap.get("id"), - individualHashMap.get("name"), - individualHashMap.get("type"), - message.activeUser, - R.xml.chip_others); - } else if (individualHashMap.get("type").equals("file")) { - itemView.setOnClickListener(v -> { - Intent browserIntent = - new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link"))); - context.startActivity(browserIntent); - }); - } - } - } - } else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.getText())) { - textSize = (float) (textSize * 2.5); - layoutParams.setWrapBefore(true); - messageTimeView.setTextColor(context.getResources().getColor(R.color.warm_grey_four)); - itemView.setSelected(true); - } - - Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources(); - if (message.grouped) { - Drawable bubbleDrawable = - DisplayUtils.INSTANCE.getMessageSelector( - resources.getColor(R.color.bg_message_list_outcoming_bubble), - resources.getColor(R.color.transparent), - resources.getColor(R.color.bg_message_list_outcoming_bubble), - R.drawable.shape_grouped_outcoming_message); - ViewCompat.setBackground(bubble, bubbleDrawable); - } else { - Drawable bubbleDrawable = DisplayUtils.INSTANCE.getMessageSelector( - resources.getColor(R.color.bg_message_list_outcoming_bubble), - resources.getColor(R.color.transparent), - resources.getColor(R.color.bg_message_list_outcoming_bubble), - R.drawable.shape_outcoming_message); - ViewCompat.setBackground(bubble, bubbleDrawable); - } - - messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - messageTimeView.setLayoutParams(layoutParams); - messageText.setText(messageString); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt new file mode 100644 index 000000000..ca12e3665 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.kt @@ -0,0 +1,118 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.adapters.messages + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.text.Spannable +import android.text.SpannableString +import android.util.TypedValue +import android.view.View +import android.widget.TextView +import androidx.core.view.ViewCompat +import androidx.emoji.widget.EmojiTextView +import butterknife.BindView +import butterknife.ButterKnife +import com.google.android.flexbox.FlexboxLayout +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector +import com.nextcloud.talk.utils.DisplayUtils.searchAndReplaceWithMentionSpan +import com.nextcloud.talk.utils.TextMatchers +import com.nextcloud.talk.utils.database.user.UserUtils +import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder +import org.koin.core.KoinComponent +import org.koin.core.inject +import java.util.* + +class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewHolder(itemView), KoinComponent { + @JvmField + @BindView(R.id.messageText) + var messageText: EmojiTextView? = null + @JvmField + @BindView(R.id.messageTime) + var messageTimeView: TextView? = null + val userUtils: UserUtils by inject() + val context: Context by inject() + private val realView: View + override fun onBind(message: ChatMessage) { + super.onBind(message) + val messageParameters: HashMap> = message.messageParameters + var messageString: Spannable = SpannableString(message.text) + realView.setSelected(false) + messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.white60)) + val layoutParams = messageTimeView!!.layoutParams as FlexboxLayout.LayoutParams + layoutParams.isWrapBefore = false + var textSize = context!!.resources.getDimension(R.dimen.chat_text_size) + if (messageParameters != null && messageParameters.size > 0) { + for (key in messageParameters.keys) { + val individualHashMap: HashMap? = message.messageParameters[key] + if (individualHashMap != null) { + if (individualHashMap["type"] == "user" || (individualHashMap["type"] + == "guest") || individualHashMap["type"] == "call") { + messageString = searchAndReplaceWithMentionSpan(messageText!!.context, + messageString, + individualHashMap["id"]!!, + individualHashMap["name"]!!, + individualHashMap["type"]!!, + message.activeUser, + R.xml.chip_others) + } else if (individualHashMap["type"] == "file") { + realView.setOnClickListener(View.OnClickListener { v: View? -> + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap["link"])) + context!!.startActivity(browserIntent) + }) + } + } + } + } else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) { + textSize = (textSize * 2.5).toFloat() + layoutParams.isWrapBefore = true + messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.warm_grey_four)) + realView.setSelected(true) + } + val resources = sharedApplication!!.resources + if (message.grouped) { + val bubbleDrawable = getMessageSelector( + resources.getColor(R.color.bg_message_list_outcoming_bubble), + resources.getColor(R.color.transparent), + resources.getColor(R.color.bg_message_list_outcoming_bubble), + R.drawable.shape_grouped_outcoming_message) + ViewCompat.setBackground(bubble, bubbleDrawable) + } else { + val bubbleDrawable = getMessageSelector( + resources.getColor(R.color.bg_message_list_outcoming_bubble), + resources.getColor(R.color.transparent), + resources.getColor(R.color.bg_message_list_outcoming_bubble), + R.drawable.shape_outcoming_message) + ViewCompat.setBackground(bubble, bubbleDrawable) + } + messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) + messageTimeView!!.layoutParams = layoutParams + messageText!!.text = messageString + } + + init { + ButterKnife.bind(this, itemView) + this.realView = itemView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.kt index 2ed2dca96..8b72800a4 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.kt @@ -30,24 +30,16 @@ import android.net.Uri import android.os.Handler import android.view.View import androidx.emoji.widget.EmojiTextView -import autodagger.AutoInjector import butterknife.BindView import butterknife.ButterKnife import coil.api.load import coil.transform.CircleCropTransformation -import com.nextcloud.talk.R.drawable -import com.nextcloud.talk.R.id -import com.nextcloud.talk.R.string -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.R.* import com.nextcloud.talk.components.filebrowser.models.BrowserFile import com.nextcloud.talk.components.filebrowser.models.DavResponse import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation import com.nextcloud.talk.models.json.chat.ChatMessage -import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE -import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE -import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE -import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE +import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.* import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.utils.AccountUtils.canWeOpenFilesApp import com.nextcloud.talk.utils.DisplayUtils.setClickableString @@ -60,21 +52,17 @@ import io.reactivex.SingleObserver import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import okhttp3.OkHttpClient -import javax.inject.Inject +import org.koin.core.KoinComponent +import org.koin.core.inject -@AutoInjector(NextcloudTalkApplication::class) class MagicPreviewMessageViewHolder(itemView: View?) : IncomingImageMessageViewHolder( itemView -) { +), KoinComponent { @JvmField @BindView(id.messageText) var messageText: EmojiTextView? = null - @JvmField - @Inject - var context: Context? = null - @JvmField - @Inject - var okHttpClient: OkHttpClient? = null + val context: Context by inject() + val okHttpClient: OkHttpClient by inject() @SuppressLint("SetTextI18n") override fun onBind(message: ChatMessage) { @@ -223,6 +211,5 @@ class MagicPreviewMessageViewHolder(itemView: View?) : IncomingImageMessageViewH init { ButterKnife.bind(this, itemView!!) - sharedApplication!!.componentApplication.inject(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicSystemMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicSystemMessageViewHolder.java deleted file mode 100644 index 95ee871d2..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicSystemMessageViewHolder.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.adapters.messages; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.text.Spannable; -import android.text.SpannableString; -import android.view.View; -import androidx.core.view.ViewCompat; -import autodagger.AutoInjector; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.models.json.chat.ChatMessage; -import com.nextcloud.talk.utils.DisplayUtils; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.stfalcon.chatkit.messages.MessageHolders; -import java.util.Map; -import javax.inject.Inject; - -@AutoInjector(NextcloudTalkApplication.class) -public class MagicSystemMessageViewHolder - extends MessageHolders.IncomingTextMessageViewHolder { - - @Inject - AppPreferences appPreferences; - - @Inject - Context context; - - public MagicSystemMessageViewHolder(View itemView) { - super(itemView); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - } - - @Override - public void onBind(ChatMessage message) { - super.onBind(message); - - Resources resources = itemView.getResources(); - int normalColor = resources.getColor(R.color.bg_message_list_incoming_bubble); - int pressedColor; - int mentionColor; - - pressedColor = normalColor; - mentionColor = resources.getColor(R.color.nc_author_text); - - Drawable bubbleDrawable = DisplayUtils.INSTANCE.getMessageSelector(normalColor, - resources.getColor(R.color.transparent), pressedColor, - R.drawable.shape_grouped_incoming_message); - ViewCompat.setBackground(bubble, bubbleDrawable); - - Spannable messageString = new SpannableString(message.getText()); - - if (message.getMessageParameters() != null && message.getMessageParameters().size() > 0) { - for (String key : message.getMessageParameters().keySet()) { - Map individualHashMap = message.getMessageParameters().get(key); - if (individualHashMap != null && (individualHashMap.get("type").equals("user") - || individualHashMap.get("type").equals("guest") - || individualHashMap.get("type").equals("call"))) { - messageString = - DisplayUtils.INSTANCE.searchAndColor(messageString, "@" + individualHashMap.get("name"), - mentionColor); - } - } - } - - text.setText(messageString); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicSystemMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicSystemMessageViewHolder.kt new file mode 100644 index 000000000..0d6fa0b50 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicSystemMessageViewHolder.kt @@ -0,0 +1,65 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.adapters.messages + +import android.content.Context +import android.text.Spannable +import android.text.SpannableString +import android.view.View +import androidx.core.view.ViewCompat +import com.nextcloud.talk.R +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector +import com.nextcloud.talk.utils.DisplayUtils.searchAndColor +import com.nextcloud.talk.utils.preferences.AppPreferences +import com.stfalcon.chatkit.messages.MessageHolders.IncomingTextMessageViewHolder +import org.koin.core.KoinComponent +import org.koin.core.inject +import java.util.* + +class MagicSystemMessageViewHolder(itemView: View?) : IncomingTextMessageViewHolder(itemView), KoinComponent { + val appPreferences: AppPreferences by inject() + val context: Context by inject() + + override fun onBind(message: ChatMessage) { + super.onBind(message) + val resources = itemView.resources + val normalColor = resources.getColor(R.color.bg_message_list_incoming_bubble) + val pressedColor: Int + val mentionColor: Int + pressedColor = normalColor + mentionColor = resources.getColor(R.color.nc_author_text) + val bubbleDrawable = getMessageSelector(normalColor, + resources.getColor(R.color.transparent), pressedColor, + R.drawable.shape_grouped_incoming_message) + ViewCompat.setBackground(bubble, bubbleDrawable) + var messageString: Spannable = SpannableString(message.text) + if (message.messageParameters != null && message.messageParameters.size > 0) { + for (key in message.messageParameters.keys) { + val individualHashMap: HashMap? = message.messageParameters[key] + if (individualHashMap != null && (individualHashMap["type"] == "user" || individualHashMap["type"] == "guest" || individualHashMap["type"] == "call")) { + messageString = searchAndColor(messageString, "@" + individualHashMap["name"], + mentionColor) + } + } + } + text.text = messageString + } +} diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index 1cdf0268a..03a077c76 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -33,8 +33,6 @@ import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.PeriodicWorkRequest import androidx.work.WorkManager -import autodagger.AutoComponent -import autodagger.AutoInjector import coil.Coil import coil.ImageLoader import com.bluelinelabs.logansquare.LoganSquare @@ -43,10 +41,6 @@ import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.imagepipeline.core.ImagePipelineConfig import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.components.filebrowser.webdav.DavUtils -import com.nextcloud.talk.dagger.modules.BusModule -import com.nextcloud.talk.dagger.modules.ContextModule -import com.nextcloud.talk.dagger.modules.DatabaseModule -import com.nextcloud.talk.dagger.modules.RestModule import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.jobs.CapabilitiesWorker import com.nextcloud.talk.jobs.PushRegistrationWorker @@ -61,14 +55,10 @@ import com.nextcloud.talk.newarch.di.module.StorageModule import com.nextcloud.talk.newarch.features.conversationsList.di.module.ConversationsListModule import com.nextcloud.talk.newarch.local.dao.UsersDao import com.nextcloud.talk.newarch.local.models.UserNgEntity -import com.nextcloud.talk.newarch.local.models.other.UserStatus.ACTIVE -import com.nextcloud.talk.newarch.local.models.other.UserStatus.DORMANT -import com.nextcloud.talk.newarch.local.models.other.UserStatus.PENDING_DELETE +import com.nextcloud.talk.newarch.local.models.other.UserStatus.* import com.nextcloud.talk.utils.ClosedInterfaceImpl import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.OkHttpNetworkFetcherWithCache -import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageModule -import com.nextcloud.talk.utils.database.user.UserModule import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.webrtc.MagicWebRTCUtils @@ -89,24 +79,11 @@ import org.webrtc.voiceengine.WebRtcAudioManager import org.webrtc.voiceengine.WebRtcAudioUtils import java.security.Security import java.util.concurrent.TimeUnit -import javax.inject.Inject -import javax.inject.Singleton -@AutoComponent( - modules = [BusModule::class, ContextModule::class, DatabaseModule::class, RestModule::class, UserModule::class, ArbitraryStorageModule::class] -) -@Singleton -@AutoInjector(NextcloudTalkApplication::class) class NextcloudTalkApplication : Application(), LifecycleObserver { - //region Fields (components) - lateinit var componentApplication: NextcloudTalkApplicationComponent - private set - //endregion - //region Getters - @Inject - lateinit var userUtils: UserUtils + val userUtils: UserUtils by inject() val imageLoader: ImageLoader by inject() val appPreferences: AppPreferences by inject() val okHttpClient: OkHttpClient by inject() @@ -151,10 +128,9 @@ class NextcloudTalkApplication : Application(), LifecycleObserver { initializeWebRtc() DisplayUtils.useCompatVectorIfNeeded() - buildComponent() + startKoin() DavUtils.registerCustomFactories() - componentApplication.inject(this) Coil.setDefaultImageLoader(imageLoader) migrateUsers() @@ -219,16 +195,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver { //endregion //region Protected methods - protected fun buildComponent() { - componentApplication = DaggerNextcloudTalkApplicationComponent.builder() - .busModule(BusModule()) - .contextModule(ContextModule(applicationContext)) - .databaseModule(DatabaseModule()) - .restModule(RestModule(applicationContext)) - .userModule(UserModule()) - .arbitraryStorageModule(ArbitraryStorageModule()) - .build() - + protected fun startKoin() { startKoin { androidContext(this@NextcloudTalkApplication) androidLogger() diff --git a/app/src/main/java/com/nextcloud/talk/components/filebrowser/adapters/items/BrowserFileItem.kt b/app/src/main/java/com/nextcloud/talk/components/filebrowser/adapters/items/BrowserFileItem.kt index 2062fbc7a..6ec50c297 100644 --- a/app/src/main/java/com/nextcloud/talk/components/filebrowser/adapters/items/BrowserFileItem.kt +++ b/app/src/main/java/com/nextcloud/talk/components/filebrowser/adapters/items/BrowserFileItem.kt @@ -26,7 +26,6 @@ import android.view.View import android.widget.CheckBox import android.widget.ImageView import android.widget.TextView -import autodagger.AutoInjector import butterknife.BindView import butterknife.ButterKnife import coil.api.load @@ -34,7 +33,6 @@ import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.components.filebrowser.models.BrowserFile import com.nextcloud.talk.interfaces.SelectionInterface -import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.getCredentials import com.nextcloud.talk.newarch.utils.getCredentials @@ -46,25 +44,17 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.IFilterable import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.viewholders.FlexibleViewHolder -import javax.inject.Inject +import org.koin.core.KoinComponent +import org.koin.core.inject -@AutoInjector(NextcloudTalkApplication::class) class BrowserFileItem( val model: BrowserFile, private val activeUser: UserNgEntity, private val selectionInterface: SelectionInterface -) : AbstractFlexibleItem(), IFilterable { - @JvmField - @Inject - var context: Context? = null +) : AbstractFlexibleItem(), IFilterable, KoinComponent { + val context: Context by inject() var isSelected: Boolean = false - init { - NextcloudTalkApplication.sharedApplication!! - .componentApplication - .inject(this) - } - override fun equals(other: Any?): Boolean { if (other is BrowserFileItem) { val inItem = other as BrowserFileItem? diff --git a/app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.java b/app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.java index 949d4893d..0be6021c1 100644 --- a/app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.java +++ b/app/src/main/java/com/nextcloud/talk/components/filebrowser/controllers/BrowserController.java @@ -70,7 +70,6 @@ import okhttp3.OkHttpClient; import org.parceler.Parcel; import org.parceler.Parcels; -@AutoInjector(NextcloudTalkApplication.class) public class BrowserController extends BaseController implements ListingInterface, FlexibleAdapter.OnItemClickListener, SelectionInterface { private final Set selectedPaths; @@ -83,8 +82,6 @@ public class BrowserController extends BaseController implements ListingInterfac @BindView(R.id.action_refresh) BottomNavigationItemView actionRefreshMenuItem; @Inject - Context context; - @Inject OkHttpClient okHttpClient; private MenuItem filesSelectionDoneMenuItem; @@ -102,9 +99,6 @@ public class BrowserController extends BaseController implements ListingInterfac public BrowserController(Bundle args) { super(); setHasOptionsMenu(true); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); browserType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_BROWSER_TYPE())); activeUser = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY())); roomToken = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN()); diff --git a/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java b/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java index e462999a2..bdcb6637c 100644 --- a/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java +++ b/app/src/main/java/com/nextcloud/talk/components/filebrowser/webdav/ReadFilesystemOperation.java @@ -20,18 +20,19 @@ package com.nextcloud.talk.components.filebrowser.webdav; -import at.bitfire.dav4jvm.DavResource; -import at.bitfire.dav4jvm.Response; -import at.bitfire.dav4jvm.exception.DavException; import com.nextcloud.talk.components.filebrowser.models.BrowserFile; import com.nextcloud.talk.components.filebrowser.models.DavResponse; import com.nextcloud.talk.dagger.modules.RestModule; -import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.newarch.local.models.UserNgEntity; import com.nextcloud.talk.utils.ApiUtils; + import java.io.IOException; import java.util.ArrayList; import java.util.List; + +import at.bitfire.dav4jvm.DavResource; +import at.bitfire.dav4jvm.Response; +import at.bitfire.dav4jvm.exception.DavException; import kotlin.Unit; import kotlin.jvm.functions.Function2; import okhttp3.HttpUrl; @@ -68,7 +69,7 @@ public class ReadFilesystemOperation { new Function2() { @Override public Unit invoke(Response response, Response.HrefRelation hrefRelation) { - davResponse.setResponse(response); + davResponse.data = response; switch (hrefRelation) { case MEMBER: memberElements.add(response); @@ -95,7 +96,7 @@ public class ReadFilesystemOperation { memberElement.getHref().toString().substring(basePath.length()))); } - davResponse.setData(remoteFiles); + davResponse.data = remoteFiles; return davResponse; } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.kt b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.kt index 426f28c1a..b895c04a0 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.kt @@ -31,7 +31,6 @@ import android.widget.TextView import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager -import autodagger.AutoInjector import butterknife.BindView import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler @@ -67,14 +66,10 @@ import org.greenrobot.eventbus.ThreadMode import org.koin.core.KoinComponent import org.koin.core.inject import java.net.CookieManager -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class AccountVerificationController(args: Bundle?) : BaseController(), KoinComponent { - @JvmField @Inject - internal var ncApi: NcApi? = null - + val ncApi: NcApi by inject() val cookieManager: CookieManager by inject() val usersRepository: UsersRepository by inject() val usersDao: UsersDao by inject() @@ -120,10 +115,6 @@ class AccountVerificationController(args: Bundle?) : BaseController(), KoinCompo override fun onViewBound(view: View) { super.onViewBound(view) - NextcloudTalkApplication.sharedApplication!! - .componentApplication - .inject(this) - if (activity != null) { activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt b/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt index 3adf42e3d..4c5d7570d 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallController.kt @@ -141,7 +141,6 @@ import org.webrtc.VideoSource import org.webrtc.VideoTrack import pub.devrel.easypermissions.AfterPermissionGranted -@AutoInjector(NextcloudTalkApplication::class) class CallController(args: Bundle) : BaseController() { @JvmField @@ -189,13 +188,8 @@ class CallController(args: Bundle) : BaseController() { @BindView(R.id.progress_bar) var progressBar: ProgressBar? = null - @JvmField - @Inject - var ncApi: NcApi? = null - @JvmField - @Inject - var userUtils: UserUtils? = null - + val userUtils: UserUtils by inject() + val ncApi: NcApi by inject() val cookieManager: CookieManager by inject() private var peerConnectionFactory: PeerConnectionFactory? = null private var audioConstraints: MediaConstraints? = null @@ -259,10 +253,6 @@ class CallController(args: Bundle) : BaseController() { get() = currentCallStatus == CallStatus.ESTABLISHED || currentCallStatus == CallStatus.IN_CONVERSATION init { - NextcloudTalkApplication.sharedApplication!! - .componentApplication - .inject(this) - roomId = args.getString(BundleKeys.KEY_ROOM_ID, "") roomToken = args.getString(BundleKeys.KEY_ROOM_TOKEN, "") conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt index 10cd06402..f1364d220 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt @@ -42,7 +42,6 @@ import android.widget.ImageView import android.widget.RelativeLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout -import autodagger.AutoInjector import butterknife.BindView import butterknife.OnClick import coil.api.load @@ -79,21 +78,15 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import org.koin.android.ext.android.inject import org.michaelevans.colorart.library.ColorArt import org.parceler.Parcels import java.io.IOException -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class CallNotificationController(private val originalBundle: Bundle) : BaseController() { - @JvmField - @Inject - internal var ncApi: NcApi? = null - - @JvmField - @Inject - internal var arbitraryStorageUtils: ArbitraryStorageUtils? = null + val ncApi: NcApi by inject() + val arbitraryStorageUtils: ArbitraryStorageUtils by inject() @JvmField @BindView(R.id.conversationNameTextView) @@ -128,10 +121,6 @@ class CallNotificationController(private val originalBundle: Bundle) : BaseContr private var handler: Handler? = null init { - NextcloudTalkApplication.sharedApplication!! - .componentApplication - .inject(this) - this.roomId = originalBundle.getString(BundleKeys.KEY_ROOM_ID, "") this.currentConversation = Parcels.unwrap(originalBundle.getParcelable(BundleKeys.KEY_ROOM)) this.userBeingCalled = originalBundle.getParcelable(BundleKeys.KEY_USER_ENTITY) @@ -296,7 +285,7 @@ class CallNotificationController(private val originalBundle: Bundle) : BaseContr } var importantConversation = false - val arbitraryStorageEntity: ArbitraryStorageEntity? = arbitraryStorageUtils!!.getStorageSetting( + val arbitraryStorageEntity: ArbitraryStorageEntity? = arbitraryStorageUtils.getStorageSetting( userBeingCalled!!.id, "important_conversation", currentConversation!!.token diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 6f8930d45..4bb57cfad 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -49,7 +49,6 @@ import androidx.emoji.text.EmojiCompat import androidx.emoji.widget.EmojiEditText import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import autodagger.AutoInjector import butterknife.BindView import butterknife.OnClick import coil.api.load @@ -123,19 +122,14 @@ import java.util.Date import java.util.HashMap import java.util.Objects import java.util.concurrent.TimeUnit -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class ChatController(args: Bundle) : BaseController(), MessagesListAdapter .OnLoadMoreListener, MessagesListAdapter.Formatter, MessagesListAdapter .OnMessageLongClickListener, MessageHolders.ContentChecker { - @Inject - @JvmField - var ncApi: NcApi? = null - @Inject - @JvmField - var userUtils: UserUtils? = null + val ncApi: NcApi by inject() + val userUtils: UserUtils by inject() + @BindView(R.id.messagesListView) @JvmField var messagesListView: MessagesList? = null @@ -199,7 +193,6 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter init { setHasOptionsMenu(true) - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) this.conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY) this.roomId = args.getString(BundleKeys.KEY_ROOM_ID, "") @@ -1517,7 +1510,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter companion object { private val TAG = "ChatController" - private val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1 - private val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2 + val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1 + val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2 } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt index 97f72e794..1870e26f0 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.kt @@ -101,7 +101,6 @@ import java.util.HashMap import java.util.HashSet import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class ContactsController : BaseController, SearchView.OnQueryTextListener, FlexibleAdapter.OnItemClickListener, @@ -109,7 +108,7 @@ class ContactsController : BaseController, FlexibleAdapter.EndlessScrollListener { val usersRepository: UsersRepository by inject() - + val ncApi: NcApi by inject() @JvmField @BindView(R.id.initial_relative_layout) var initialRelativeLayout: RelativeLayout? = null @@ -143,9 +142,6 @@ class ContactsController : BaseController, @BindView(R.id.generic_rv_layout) var genericRvLayout: CoordinatorLayout? = null - @JvmField - @Inject - var ncApi: NcApi? = null private var credentials: String? = null private var currentUser: UserNgEntity? = null private var adapter: FlexibleAdapter>? = null diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java deleted file mode 100644 index ec0d66444..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.controllers; - -import android.app.Activity; -import android.app.KeyguardManager; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.biometric.BiometricPrompt; -import androidx.fragment.app.FragmentActivity; -import autodagger.AutoInjector; -import butterknife.OnClick; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.controllers.base.BaseController; -import com.nextcloud.talk.utils.SecurityUtils; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import javax.inject.Inject; - -@AutoInjector(NextcloudTalkApplication.class) -public class LockedController extends BaseController { - public static final String TAG = "LockedController"; - private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112; - - @Inject - AppPreferences appPreferences; - - @Override - protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { - return inflater.inflate(R.layout.controller_locked, container, false); - } - - @Override - protected void onViewBound(@NonNull View view) { - super.onViewBound(view); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - if (getActionBar() != null) { - getActionBar().hide(); - } - } - - @RequiresApi(api = Build.VERSION_CODES.M) - @Override - protected void onAttach(@NonNull View view) { - super.onAttach(view); - checkIfWeAreSecure(); - } - - @RequiresApi(api = Build.VERSION_CODES.M) - @OnClick(R.id.unlockTextView) - void unlock() { - checkIfWeAreSecure(); - } - - @RequiresApi(api = Build.VERSION_CODES.M) - private void showBiometricDialog() { - Context context = getActivity(); - - if (context != null) { - final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() - .setTitle(String.format(context.getString(R.string.nc_biometric_unlock), - context.getString(R.string.nc_app_name))) - .setNegativeButtonText(context.getString(R.string.nc_cancel)) - .build(); - - Executor executor = Executors.newSingleThreadExecutor(); - - final BiometricPrompt biometricPrompt = - new BiometricPrompt((FragmentActivity) context, executor, - new BiometricPrompt.AuthenticationCallback() { - @Override - public void onAuthenticationSucceeded( - @NonNull BiometricPrompt.AuthenticationResult result) { - super.onAuthenticationSucceeded(result); - Log.d(TAG, "Fingerprint recognised successfully"); - new Handler(Looper.getMainLooper()).post( - () -> getRouter().popCurrentController()); - } - - @Override - public void onAuthenticationFailed() { - super.onAuthenticationFailed(); - Log.d(TAG, "Fingerprint not recognised"); - } - - @Override - public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { - super.onAuthenticationError(errorCode, errString); - showAuthenticationScreen(); - } - } - ); - - BiometricPrompt.CryptoObject cryptoObject = SecurityUtils.getCryptoObject(); - if (cryptoObject != null) { - biometricPrompt.authenticate(promptInfo, cryptoObject); - } else { - biometricPrompt.authenticate(promptInfo); - } - } - } - - @RequiresApi(api = Build.VERSION_CODES.M) - private void checkIfWeAreSecure() { - if (getActivity() != null) { - KeyguardManager keyguardManager = - (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE); - if (keyguardManager != null - && keyguardManager.isKeyguardSecure() - && appPreferences.getIsScreenLocked()) { - if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) { - showBiometricDialog(); - } else { - getRouter().popCurrentController(); - } - } - } - } - - private void showAuthenticationScreen() { - if (getActivity() != null) { - KeyguardManager keyguardManager = - (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE); - Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null); - if (intent != null) { - startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); - } - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { - if (resultCode == Activity.RESULT_OK) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) { - Log.d(TAG, "All went well, dismiss locked controller"); - getRouter().popCurrentController(); - } - } - } else { - Log.d(TAG, "Authorization failed"); - } - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt new file mode 100644 index 000000000..fe6350d05 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.kt @@ -0,0 +1,152 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.controllers + +import android.app.Activity +import android.app.KeyguardManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricPrompt +import androidx.biometric.BiometricPrompt.PromptInfo +import androidx.fragment.app.FragmentActivity +import butterknife.OnClick +import com.nextcloud.talk.R +import com.nextcloud.talk.controllers.base.BaseController +import com.nextcloud.talk.utils.SecurityUtils +import com.nextcloud.talk.utils.preferences.AppPreferences +import org.koin.android.ext.android.inject +import java.util.concurrent.Executor +import java.util.concurrent.Executors + +class LockedController : BaseController() { + override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + return inflater.inflate(R.layout.controller_locked, container, false) + } + + override fun onViewBound(view: View) { + super.onViewBound(view) + if (actionBar != null) { + actionBar!!.hide() + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + override fun onAttach(view: View) { + super.onAttach(view) + checkIfWeAreSecure() + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @OnClick(R.id.unlockTextView) + fun unlock() { + checkIfWeAreSecure() + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private fun showBiometricDialog() { + val context: Context? = activity + if (context != null) { + val promptInfo = PromptInfo.Builder() + .setTitle(String.format(context.getString(R.string.nc_biometric_unlock), + context.getString(R.string.nc_app_name))) + .setNegativeButtonText(context.getString(R.string.nc_cancel)) + .build() + val executor: Executor = Executors.newSingleThreadExecutor() + val biometricPrompt = BiometricPrompt((context as FragmentActivity?)!!, executor, + object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationSucceeded( + result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + Log.d(TAG, "Fingerprint recognised successfully") + Handler(Looper.getMainLooper()).post { router.popCurrentController() } + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + Log.d(TAG, "Fingerprint not recognised") + } + + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + showAuthenticationScreen() + } + } + ) + val cryptoObject = SecurityUtils.getCryptoObject() + if (cryptoObject != null) { + biometricPrompt.authenticate(promptInfo, cryptoObject) + } else { + biometricPrompt.authenticate(promptInfo) + } + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private fun checkIfWeAreSecure() { + if (activity != null) { + val keyguardManager = activity!!.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + if (keyguardManager != null && keyguardManager.isKeyguardSecure + && appPreferences!!.isScreenLocked) { + if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences!!.screenLockTimeout)) { + showBiometricDialog() + } else { + router.popCurrentController() + } + } + } + } + + private fun showAuthenticationScreen() { + if (activity != null) { + val keyguardManager = activity!!.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null) + intent?.let { startActivityForResult(it, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { + if (resultCode == Activity.RESULT_OK) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (SecurityUtils.checkIfWeAreAuthenticated(appPreferences!!.screenLockTimeout)) { + Log.d(TAG, "All went well, dismiss locked controller") + router.popCurrentController() + } + } + } else { + Log.d(TAG, "Authorization failed") + } + } + } + + companion object { + const val TAG = "LockedController" + private const val REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.java b/app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.java index d1b608461..48f1f9e55 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.java @@ -21,7 +21,6 @@ package com.nextcloud.talk.controllers; import android.annotation.SuppressLint; -import android.content.Context; import android.database.Cursor; import android.media.MediaPlayer; import android.media.RingtoneManager; @@ -34,29 +33,28 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import autodagger.AutoInjector; -import butterknife.BindView; + import com.bluelinelabs.logansquare.LoganSquare; import com.nextcloud.talk.R; import com.nextcloud.talk.adapters.items.NotificationSoundItem; -import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.controllers.base.BaseController; import com.nextcloud.talk.models.RingtoneSettings; import com.nextcloud.talk.utils.bundle.BundleKeys; -import com.nextcloud.talk.utils.preferences.AppPreferences; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.SelectableAdapter; import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; -@AutoInjector(NextcloudTalkApplication.class) public class RingtoneSelectionController extends BaseController implements FlexibleAdapter.OnItemClickListener { @@ -68,12 +66,6 @@ public class RingtoneSelectionController extends BaseController @BindView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout; - @Inject - AppPreferences appPreferences; - - @Inject - Context context; - private FlexibleAdapter adapter; private RecyclerView.AdapterDataObserver adapterDataObserver; private List abstractFlexibleItemList = new ArrayList<>(); @@ -97,9 +89,6 @@ public class RingtoneSelectionController extends BaseController @Override protected void onViewBound(@NonNull View view) { super.onViewBound(view); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); if (adapter == null) { adapter = new FlexibleAdapter<>(abstractFlexibleItemList, getActivity(), false); diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.java b/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.java deleted file mode 100644 index d80f0a4a7..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.controllers; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.net.Uri; -import android.os.Bundle; -import android.security.KeyChain; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.widget.ProgressBar; -import android.widget.TextView; -import androidx.annotation.NonNull; -import autodagger.AutoInjector; -import butterknife.BindView; -import butterknife.OnClick; -import com.bluelinelabs.conductor.RouterTransaction; -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; -import com.nextcloud.talk.R; -import com.nextcloud.talk.api.NcApi; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.controllers.base.BaseController; -import com.nextcloud.talk.utils.AccountUtils; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.bundle.BundleKeys; -import com.nextcloud.talk.utils.database.user.UserUtils; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder; -import com.uber.autodispose.AutoDispose; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import java.security.cert.CertificateException; -import javax.inject.Inject; -import studio.carbonylgroup.textfieldboxes.ExtendedEditText; -import studio.carbonylgroup.textfieldboxes.TextFieldBoxes; - -@AutoInjector(NextcloudTalkApplication.class) -public class ServerSelectionController extends BaseController { - - public static final String TAG = "ServerSelectionController"; - - @BindView(R.id.extended_edit_text) - ExtendedEditText serverEntry; - @BindView(R.id.text_field_boxes) - TextFieldBoxes textFieldBoxes; - @BindView(R.id.progress_bar) - ProgressBar progressBar; - @BindView(R.id.helper_text_view) - TextView providersTextView; - @BindView(R.id.cert_text_view) - TextView certTextView; - - @Inject - NcApi ncApi; - - @Inject - UserUtils userUtils; - - @Inject - AppPreferences appPreferences; - - @Override - protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { - return inflater.inflate(R.layout.controller_server_selection, container, false); - } - - @SuppressLint("LongLogTag") - @OnClick(R.id.cert_text_view) - public void onCertClick() { - if (getActivity() != null) { - KeyChain.choosePrivateKeyAlias(getActivity(), alias -> { - if (alias != null) { - appPreferences.setTemporaryClientCertAlias(alias); - } else { - appPreferences.removeTemporaryClientCertAlias(); - } - - setCertTextView(); - }, new String[] { "RSA", "EC" }, null, null, -1, null); - } - } - - @Override - protected void onViewBound(@NonNull View view) { - super.onViewBound(view); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - - if (getActivity() != null) { - getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - - if (getActionBar() != null) { - getActionBar().hide(); - } - - textFieldBoxes.getEndIconImageButton() - .setBackgroundDrawable(getResources().getDrawable(R.drawable - .ic_arrow_forward_white_24px)); - textFieldBoxes.getEndIconImageButton().setAlpha(0.5f); - textFieldBoxes.getEndIconImageButton().setEnabled(false); - textFieldBoxes.getEndIconImageButton().setVisibility(View.VISIBLE); - textFieldBoxes.getEndIconImageButton().setOnClickListener(view1 -> checkServerAndProceed()); - - if (TextUtils.isEmpty(getResources().getString(R.string.nc_providers_url)) - && (TextUtils.isEmpty(getResources - ().getString(R.string.nc_import_account_type)))) { - providersTextView.setVisibility(View.INVISIBLE); - } else { - if ((TextUtils.isEmpty(getResources - ().getString(R.string.nc_import_account_type)) || - AccountUtils.INSTANCE.findAccounts(userUtils.getUsers()).size() == 0) && - userUtils.getUsers().size() == 0) { - - providersTextView.setText(R.string.nc_get_from_provider); - providersTextView.setOnClickListener(view12 -> { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getResources() - .getString(R.string.nc_providers_url))); - startActivity(browserIntent); - }); - } else if (AccountUtils.INSTANCE.findAccounts(userUtils.getUsers()).size() > 0) { - if (!TextUtils.isEmpty(AccountUtils.INSTANCE.getAppNameBasedOnPackage(getResources() - .getString(R.string.nc_import_accounts_from)))) { - if (AccountUtils.INSTANCE.findAccounts(userUtils.getUsers()).size() > 1) { - providersTextView.setText(String.format(getResources().getString(R.string - .nc_server_import_accounts), - AccountUtils.INSTANCE.getAppNameBasedOnPackage(getResources() - .getString(R.string.nc_import_accounts_from)))); - } else { - providersTextView.setText(String.format(getResources().getString(R.string - .nc_server_import_account), - AccountUtils.INSTANCE.getAppNameBasedOnPackage(getResources() - .getString(R.string.nc_import_accounts_from)))); - } - } else { - if (AccountUtils.INSTANCE.findAccounts(userUtils.getUsers()).size() > 1) { - providersTextView.setText( - getResources().getString(R.string.nc_server_import_accounts_plain)); - } else { - providersTextView.setText(getResources().getString(R.string - .nc_server_import_account_plain)); - } - } - - providersTextView.setOnClickListener(view13 -> { - Bundle bundle = new Bundle(); - bundle.putBoolean(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT(), true); - getRouter().pushController(RouterTransaction.with( - new SwitchAccountController(bundle)) - .pushChangeHandler(new HorizontalChangeHandler()) - .popChangeHandler(new HorizontalChangeHandler())); - }); - } else { - providersTextView.setVisibility(View.INVISIBLE); - } - } - - serverEntry.requestFocus(); - - serverEntry.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - if (!textFieldBoxes.isOnError() && !TextUtils.isEmpty(serverEntry.getText())) { - toggleProceedButton(true); - } else { - toggleProceedButton(false); - } - } - }); - - serverEntry.setOnEditorActionListener((textView, i, keyEvent) -> { - if (i == EditorInfo.IME_ACTION_DONE) { - checkServerAndProceed(); - } - - return false; - }); - } - - private void toggleProceedButton(boolean show) { - textFieldBoxes.getEndIconImageButton().setEnabled(show); - - if (show) { - textFieldBoxes.getEndIconImageButton().setAlpha(1f); - } else { - textFieldBoxes.getEndIconImageButton().setAlpha(0.5f); - } - } - - private void checkServerAndProceed() { - String url = serverEntry.getText().toString().trim(); - - serverEntry.setEnabled(false); - progressBar.setVisibility(View.VISIBLE); - if (providersTextView.getVisibility() != View.INVISIBLE) { - providersTextView.setVisibility(View.INVISIBLE); - certTextView.setVisibility(View.INVISIBLE); - } - - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - } - - String queryUrl = url + ApiUtils.getUrlPostfixForStatus(); - - if (url.startsWith("http://") || url.startsWith("https://")) { - checkServer(queryUrl, false); - } else { - checkServer("https://" + queryUrl, true); - } - } - - private void checkServer(String queryUrl, boolean checkForcedHttps) { - ncApi.getServerStatus(queryUrl) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .as(AutoDispose.autoDisposable(getScopeProvider())) - .subscribe(status -> { - String productName = getResources().getString(R.string.nc_server_product_name); - - String versionString = status.getVersion().substring(0, status.getVersion().indexOf(".")); - int version = Integer.parseInt(versionString); - if (status.isInstalled() && !status.isMaintenance() && - !status.isNeedsUpgrade() && - version >= 13) { - - getRouter().pushController(RouterTransaction.with( - new WebViewLoginController(queryUrl.replace("/status.php", ""), - false)) - .pushChangeHandler(new HorizontalChangeHandler()) - .popChangeHandler(new HorizontalChangeHandler())); - } else if (!status.isInstalled()) { - textFieldBoxes.setError(String.format( - getResources().getString(R.string.nc_server_not_installed), productName), - true); - toggleProceedButton(false); - } else if (status.isNeedsUpgrade()) { - textFieldBoxes.setError(String.format(getResources(). - getString(R.string.nc_server_db_upgrade_needed), - productName), true); - toggleProceedButton(false); - } else if (status.isMaintenance()) { - textFieldBoxes.setError(String.format(getResources(). - getString(R.string.nc_server_maintenance), - productName), - true); - toggleProceedButton(false); - } else if (!status.getVersion().startsWith("13.")) { - textFieldBoxes.setError(String.format(getResources(). - getString(R.string.nc_server_version), - getResources().getString(R.string.nc_app_name) - , productName), true); - toggleProceedButton(false); - } - }, throwable -> { - if (checkForcedHttps) { - checkServer(queryUrl.replace("https://", "http://"), false); - } else { - if (throwable.getLocalizedMessage() != null) { - textFieldBoxes.setError(throwable.getLocalizedMessage(), true); - } else if (throwable.getCause() instanceof CertificateException) { - textFieldBoxes.setError(getResources().getString(R.string.nc_certificate_error), - false); - } - - if (serverEntry != null) { - serverEntry.setEnabled(true); - } - - progressBar.setVisibility(View.INVISIBLE); - if (providersTextView.getVisibility() != View.INVISIBLE) { - providersTextView.setVisibility(View.VISIBLE); - certTextView.setVisibility(View.VISIBLE); - } - toggleProceedButton(false); - } - }, () -> { - progressBar.setVisibility(View.INVISIBLE); - if (providersTextView.getVisibility() != View.INVISIBLE) { - providersTextView.setVisibility(View.VISIBLE); - certTextView.setVisibility(View.VISIBLE); - } - }); - } - - @Override - protected void onAttach(@NonNull View view) { - super.onAttach(view); - if (ApplicationWideMessageHolder.getInstance().getMessageType() != null) { - if (ApplicationWideMessageHolder.getInstance().getMessageType() - .equals(ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION)) { - textFieldBoxes.setError( - getResources().getString(R.string.nc_account_scheduled_for_deletion), - false); - ApplicationWideMessageHolder.getInstance().setMessageType(null); - } else if (ApplicationWideMessageHolder.getInstance().getMessageType() - .equals(ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK)) { - textFieldBoxes.setError(getResources().getString(R.string.nc_settings_no_talk_installed), - false); - } else if (ApplicationWideMessageHolder.getInstance().getMessageType() - .equals(ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT)) { - textFieldBoxes.setError( - getResources().getString(R.string.nc_server_failed_to_import_account), - false); - } - ApplicationWideMessageHolder.getInstance().setMessageType(null); - } - - setCertTextView(); - } - - private void setCertTextView() { - if (getActivity() != null) { - getActivity().runOnUiThread(() -> { - if (!TextUtils.isEmpty(appPreferences.getTemporaryClientCertAlias())) { - certTextView.setText(R.string.nc_change_cert_auth); - } else { - certTextView.setText(R.string.nc_configure_cert_auth); - } - - textFieldBoxes.setError("", true); - toggleProceedButton(true); - }); - } - } - - @Override - protected void onDestroyView(@NonNull View view) { - super.onDestroyView(view); - if (getActivity() != null) { - getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.kt new file mode 100644 index 000000000..3422ee390 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.kt @@ -0,0 +1,327 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.controllers + +import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.ActivityInfo +import android.net.Uri +import android.os.Bundle +import android.security.KeyChain +import android.text.Editable +import android.text.TextUtils +import android.text.TextWatcher +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.widget.ProgressBar +import android.widget.TextView +import autodagger.AutoInjector +import butterknife.BindView +import butterknife.OnClick +import com.bluelinelabs.conductor.RouterTransaction +import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler +import com.nextcloud.talk.R +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.controllers.base.BaseController +import com.nextcloud.talk.models.json.generic.Status +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.utils.AccountUtils.findAccounts +import com.nextcloud.talk.utils.AccountUtils.getAppNameBasedOnPackage +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT +import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder +import com.uber.autodispose.AutoDispose +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import org.koin.android.ext.android.inject +import studio.carbonylgroup.textfieldboxes.ExtendedEditText +import studio.carbonylgroup.textfieldboxes.TextFieldBoxes +import java.security.cert.CertificateException +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class ServerSelectionController : BaseController() { + @JvmField + @BindView(R.id.extended_edit_text) + var serverEntry: ExtendedEditText? = null + @JvmField + @BindView(R.id.text_field_boxes) + var textFieldBoxes: TextFieldBoxes? = null + @JvmField + @BindView(R.id.progress_bar) + var progressBar: ProgressBar? = null + @JvmField + @BindView(R.id.helper_text_view) + var providersTextView: TextView? = null + @JvmField + @BindView(R.id.cert_text_view) + var certTextView: TextView? = null + + val usersRepository: UsersRepository by inject() + val ncApi: NcApi by inject() + + override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + return inflater.inflate(R.layout.controller_server_selection, container, false) + } + + @SuppressLint("LongLogTag") + @OnClick(R.id.cert_text_view) + fun onCertClick() { + if (activity != null) { + KeyChain.choosePrivateKeyAlias(activity!!, { alias: String? -> + if (alias != null) { + appPreferences.temporaryClientCertAlias = alias + } else { + appPreferences.removeTemporaryClientCertAlias() + } + setCertTextView() + }, arrayOf("RSA", "EC"), null, null, -1, null) + } + } + + override fun onViewBound(view: View) { + super.onViewBound(view) + if (activity != null) { + activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + if (actionBar != null) { + actionBar!!.hide() + } + textFieldBoxes!!.endIconImageButton + .setBackgroundDrawable(resources!!.getDrawable(R.drawable.ic_arrow_forward_white_24px)) + textFieldBoxes!!.endIconImageButton.alpha = 0.5f + textFieldBoxes!!.endIconImageButton.isEnabled = false + textFieldBoxes!!.endIconImageButton.visibility = View.VISIBLE + textFieldBoxes!!.endIconImageButton.setOnClickListener { view1: View? -> checkServerAndProceed() } + if (TextUtils.isEmpty(resources!!.getString(R.string.nc_providers_url)) + && TextUtils.isEmpty(resources!!.getString(R.string.nc_import_account_type))) { + providersTextView!!.visibility = View.INVISIBLE + } else { + val users = usersRepository.getUsers() + val usersSize = users.count() + if ((TextUtils.isEmpty(resources!!.getString(R.string.nc_import_account_type)) || + findAccounts(users).isEmpty()) && + usersSize == 0) { + providersTextView!!.setText(R.string.nc_get_from_provider) + providersTextView!!.setOnClickListener { view12: View? -> + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(resources!! + .getString(R.string.nc_providers_url))) + startActivity(browserIntent) + } + } else if (findAccounts(users).isNotEmpty()) { + if (!TextUtils.isEmpty(getAppNameBasedOnPackage(resources!! + .getString(R.string.nc_import_accounts_from)))) { + if (findAccounts(users).size > 1) { + providersTextView!!.text = String.format(resources!!.getString(R.string.nc_server_import_accounts), + getAppNameBasedOnPackage(resources!! + .getString(R.string.nc_import_accounts_from))) + } else { + providersTextView!!.text = String.format(resources!!.getString(R.string.nc_server_import_account), + getAppNameBasedOnPackage(resources!! + .getString(R.string.nc_import_accounts_from))) + } + } else { + if (findAccounts(users).size > 1) { + providersTextView!!.text = resources!!.getString(R.string.nc_server_import_accounts_plain) + } else { + providersTextView!!.text = resources!!.getString(R.string.nc_server_import_account_plain) + } + } + providersTextView!!.setOnClickListener { view13: View? -> + val bundle = Bundle() + bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true) + router.pushController(RouterTransaction.with( + SwitchAccountController(bundle)) + .pushChangeHandler(HorizontalChangeHandler()) + .popChangeHandler(HorizontalChangeHandler())) + } + } else { + providersTextView!!.visibility = View.INVISIBLE + } + } + serverEntry!!.requestFocus() + serverEntry!!.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + override fun afterTextChanged(editable: Editable) { + if (!textFieldBoxes!!.isOnError && !TextUtils.isEmpty(serverEntry!!.text)) { + toggleProceedButton(true) + } else { + toggleProceedButton(false) + } + } + }) + serverEntry!!.setOnEditorActionListener { textView: TextView?, i: Int, keyEvent: KeyEvent? -> + if (i == EditorInfo.IME_ACTION_DONE) { + checkServerAndProceed() + } + false + } + } + + private fun toggleProceedButton(show: Boolean) { + textFieldBoxes!!.endIconImageButton.isEnabled = show + if (show) { + textFieldBoxes!!.endIconImageButton.alpha = 1f + } else { + textFieldBoxes!!.endIconImageButton.alpha = 0.5f + } + } + + private fun checkServerAndProceed() { + var url = serverEntry!!.text.toString().trim { it <= ' ' } + serverEntry!!.isEnabled = false + progressBar!!.visibility = View.VISIBLE + if (providersTextView!!.visibility != View.INVISIBLE) { + providersTextView!!.visibility = View.INVISIBLE + certTextView!!.visibility = View.INVISIBLE + } + if (url.endsWith("/")) { + url = url.substring(0, url.length - 1) + } + val queryUrl = url + ApiUtils.getUrlPostfixForStatus() + if (url.startsWith("http://") || url.startsWith("https://")) { + checkServer(queryUrl, false) + } else { + checkServer("https://$queryUrl", true) + } + } + + private fun checkServer(queryUrl: String, checkForcedHttps: Boolean) { + ncApi!!.getServerStatus(queryUrl) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .`as`(AutoDispose.autoDisposable(scopeProvider)) + .subscribe({ status: Status -> + val productName = resources!!.getString(R.string.nc_server_product_name) + val versionString: String = status.version.substring(0, status.version.indexOf(".")) + val version = versionString.toInt() + if (status.installed && !status.maintenance && + !status.needsUpgrade && version >= 13) { + router.pushController(RouterTransaction.with( + WebViewLoginController(queryUrl.replace("/status.php", ""), + false)) + .pushChangeHandler(HorizontalChangeHandler()) + .popChangeHandler(HorizontalChangeHandler())) + } else if (!status.installed) { + textFieldBoxes!!.setError(String.format( + resources!!.getString(R.string.nc_server_not_installed), productName), + true) + toggleProceedButton(false) + } else if (status.needsUpgrade) { + textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_db_upgrade_needed), + productName), true) + toggleProceedButton(false) + } else if (status.maintenance) { + textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_maintenance), + productName), + true) + toggleProceedButton(false) + } else if (!status.version.startsWith("13.")) { + textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_version), + resources!!.getString(R.string.nc_app_name) + , productName), true) + toggleProceedButton(false) + } + }, { throwable: Throwable -> + if (checkForcedHttps) { + checkServer(queryUrl.replace("https://", "http://"), false) + } else { + if (throwable.localizedMessage != null) { + textFieldBoxes!!.setError(throwable.localizedMessage, true) + } else if (throwable.cause is CertificateException) { + textFieldBoxes!!.setError(resources!!.getString(R.string.nc_certificate_error), + false) + } + if (serverEntry != null) { + serverEntry!!.isEnabled = true + } + progressBar!!.visibility = View.INVISIBLE + if (providersTextView!!.visibility != View.INVISIBLE) { + providersTextView!!.visibility = View.VISIBLE + certTextView!!.visibility = View.VISIBLE + } + toggleProceedButton(false) + } + }) { + progressBar!!.visibility = View.INVISIBLE + if (providersTextView!!.visibility != View.INVISIBLE) { + providersTextView!!.visibility = View.VISIBLE + certTextView!!.visibility = View.VISIBLE + } + } + } + + override fun onAttach(view: View) { + super.onAttach(view) + if (ApplicationWideMessageHolder.getInstance().messageType != null) { + when (ApplicationWideMessageHolder.getInstance().messageType + ) { + ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION -> { + textFieldBoxes!!.setError( + resources!!.getString(R.string.nc_account_scheduled_for_deletion), + false) + ApplicationWideMessageHolder.getInstance().messageType = null + } + ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK -> { + textFieldBoxes!!.setError(resources!!.getString(R.string.nc_settings_no_talk_installed), + false) + } + ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT -> { + textFieldBoxes!!.setError( + resources!!.getString(R.string.nc_server_failed_to_import_account), + false) + } + } + ApplicationWideMessageHolder.getInstance().messageType = null + } + setCertTextView() + } + + private fun setCertTextView() { + if (activity != null) { + activity!!.runOnUiThread { + if (!TextUtils.isEmpty(appPreferences.temporaryClientCertAlias)) { + certTextView!!.setText(R.string.nc_change_cert_auth) + } else { + certTextView!!.setText(R.string.nc_configure_cert_auth) + } + textFieldBoxes!!.setError("", true) + toggleProceedButton(true) + } + } + } + + override fun onDestroyView(view: View) { + super.onDestroyView(view) + if (activity != null) { + activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR + } + } + + companion object { + const val TAG = "ServerSelectionController" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt index c1f48abd0..e2cbcc5f4 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt @@ -99,7 +99,6 @@ import java.util.Locale import java.util.Objects import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class SettingsController : BaseController() { @JvmField @BindView(R.id.settings_screen) @@ -178,8 +177,7 @@ class SettingsController : BaseController() { @BindView(R.id.message_text) var messageText: TextView? = null @JvmField - @Inject - var ncApi: NcApi? = null + val ncApi: NcApi by inject() val usersRepository: UsersRepository by inject() private var saveStateHandler: LovelySaveStateHandler? = null private var currentUser: UserNgEntity? = null @@ -203,9 +201,6 @@ class SettingsController : BaseController() { setHasOptionsMenu(true) ViewCompat.setTransitionName(avatarImageView!!, "userAvatar.transitionTag") - NextcloudTalkApplication.sharedApplication!! - .componentApplication - .inject(this) if (saveStateHandler == null) { saveStateHandler = LovelySaveStateHandler() diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.java b/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.java index 7674c4528..e32f11169 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.java @@ -29,12 +29,15 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import autodagger.AutoInjector; import butterknife.BindView; + import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; import com.nextcloud.talk.R; @@ -44,208 +47,208 @@ import com.nextcloud.talk.controllers.base.BaseController; import com.nextcloud.talk.models.ImportAccount; import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.json.participants.Participant; +import com.nextcloud.talk.newarch.local.models.UserNgEntity; import com.nextcloud.talk.utils.AccountUtils; import com.nextcloud.talk.utils.bundle.BundleKeys; import com.nextcloud.talk.utils.database.user.UserUtils; import com.uber.autodispose.AutoDispose; + import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import io.reactivex.Observer; import io.reactivex.disposables.Disposable; + import java.net.CookieManager; import java.util.ArrayList; import java.util.List; + import javax.inject.Inject; -@AutoInjector(NextcloudTalkApplication.class) public class SwitchAccountController extends BaseController { - @Inject - UserUtils userUtils; + @Inject + UserUtils userUtils; - @BindView(R.id.recyclerView) - RecyclerView recyclerView; + @BindView(R.id.recyclerView) + RecyclerView recyclerView; - @Inject - CookieManager cookieManager; + @Inject + CookieManager cookieManager; - @BindView(R.id.swipe_refresh_layout) - SwipeRefreshLayout swipeRefreshLayout; - private FlexibleAdapter adapter; - private List userItems = new ArrayList<>(); + @BindView(R.id.swipe_refresh_layout) + SwipeRefreshLayout swipeRefreshLayout; + private FlexibleAdapter adapter; + private List userItems = new ArrayList<>(); - private boolean isAccountImport = false; + private boolean isAccountImport = false; - private FlexibleAdapter.OnItemClickListener onImportItemClickListener = - new FlexibleAdapter.OnItemClickListener() { - @Override - public boolean onItemClick(View view, int position) { - if (userItems.size() > position) { - Account account = ((AdvancedUserItem) userItems.get(position)).getAccount(); - reauthorizeFromImport(account); - } - - return true; - } - }; - - private FlexibleAdapter.OnItemClickListener onSwitchItemClickListener = - new FlexibleAdapter.OnItemClickListener() { - @Override - public boolean onItemClick(View view, int position) { - if (userItems.size() > position) { - UserEntity userEntity = ((AdvancedUserItem) userItems.get(position)).getEntity(); - userUtils.createOrUpdateUser(null, - null, null, null, - null, true, null, userEntity.getId(), null, null, null) - .as(AutoDispose.autoDisposable(getScopeProvider())) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onNext(UserEntity userEntity) { - cookieManager.getCookieStore().removeAll(); - - userUtils.disableAllUsersWithoutId(userEntity.getId()); - if (getActivity() != null) { - getActivity().runOnUiThread(() -> getRouter().popCurrentController()); + private FlexibleAdapter.OnItemClickListener onImportItemClickListener = + new FlexibleAdapter.OnItemClickListener() { + @Override + public boolean onItemClick(View view, int position) { + if (userItems.size() > position) { + Account account = ((AdvancedUserItem) userItems.get(position)).getAccount(); + reauthorizeFromImport(account); } - } - @Override - public void onError(Throwable e) { + return true; + } + }; - } + private FlexibleAdapter.OnItemClickListener onSwitchItemClickListener = + new FlexibleAdapter.OnItemClickListener() { + @Override + public boolean onItemClick(View view, int position) { + if (userItems.size() > position) { + UserEntity userEntity = ((AdvancedUserItem) userItems.get(position)).getEntity(); + userUtils.createOrUpdateUser(null, + null, null, null, + null, true, null, userEntity.getId(), null, null, null) + .as(AutoDispose.autoDisposable(getScopeProvider())) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { - @Override - public void onComplete() { + } - } - }); - } + @Override + public void onNext(UserEntity userEntity) { + cookieManager.getCookieStore().removeAll(); - return true; + userUtils.disableAllUsersWithoutId(userEntity.getId()); + if (getActivity() != null) { + getActivity().runOnUiThread(() -> getRouter().popCurrentController()); + } + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }); + } + + return true; + } + }; + + public SwitchAccountController() { + setHasOptionsMenu(true); + } + + public SwitchAccountController(Bundle args) { + super(); + setHasOptionsMenu(true); + + if (args.containsKey(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT())) { + isAccountImport = true; } - }; - - public SwitchAccountController() { - setHasOptionsMenu(true); - } - - public SwitchAccountController(Bundle args) { - super(); - setHasOptionsMenu(true); - - if (args.containsKey(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT())) { - isAccountImport = true; - } - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - getRouter().popCurrentController(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { - return inflater.inflate(R.layout.controller_generic_rv, container, false); - } - - @Override - protected void onViewBound(@NonNull View view) { - super.onViewBound(view); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - swipeRefreshLayout.setEnabled(false); - - if (getActionBar() != null) { - getActionBar().show(); } - if (adapter == null) { - adapter = new FlexibleAdapter<>(userItems, getActivity(), false); + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + getRouter().popCurrentController(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } - UserEntity userEntity; - Participant participant; + @Override + protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { + return inflater.inflate(R.layout.controller_generic_rv, container, false); + } - if (!isAccountImport) { - for (Object userEntityObject : userUtils.getUsers()) { - userEntity = (UserEntity) userEntityObject; - if (!userEntity.getCurrent()) { - participant = new Participant(); - participant.setName(userEntity.getDisplayName()); + @Override + protected void onViewBound(@NonNull View view) { + super.onViewBound(view); + swipeRefreshLayout.setEnabled(false); - String userId; + if (getActionBar() != null) { + getActionBar().show(); + } - if (userEntity.getUserId() != null) { - userId = userEntity.getUserId(); + if (adapter == null) { + adapter = new FlexibleAdapter<>(userItems, getActivity(), false); + + UserNgEntity userEntity; + Participant participant; + + if (!isAccountImport) { + for (Object userEntityObject : userUtils.getUsers()) { + userEntity = (UserEntity) userEntityObject; + if (!userEntity.getCurrent()) { + participant = new Participant(); + participant.setName(userEntity.getDisplayName()); + + String userId; + + if (userEntity.getUserId() != null) { + userId = userEntity.getUserId(); + } else { + userId = userEntity.getUsername(); + } + participant.setUserId(userId); + userItems.add(new AdvancedUserItem(participant, userEntity, null)); + } + } + + adapter.addListener(onSwitchItemClickListener); + adapter.updateDataSet(userItems, false); } else { - userId = userEntity.getUsername(); + Account account; + ImportAccount importAccount; + for (Object accountObject : AccountUtils.INSTANCE.findAccounts(userUtils.getUsers())) { + account = (Account) accountObject; + importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account); + + participant = new Participant(); + participant.name = importAccount.username; + participant.userId = importAccount.username; + userEntity = new UserEntity(); + userEntity.setBaseUrl(importAccount.getBaseUrl()); + userItems.add(new AdvancedUserItem(participant, userEntity, account)); + } + + adapter.addListener(onImportItemClickListener); + adapter.updateDataSet(userItems, false); } - participant.setUserId(userId); - userItems.add(new AdvancedUserItem(participant, userEntity, null)); - } } - adapter.addListener(onSwitchItemClickListener); - adapter.updateDataSet(userItems, false); - } else { - Account account; - ImportAccount importAccount; - for (Object accountObject : AccountUtils.INSTANCE.findAccounts(userUtils.getUsers())) { - account = (Account) accountObject; - importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account); - - participant = new Participant(); - participant.setName(importAccount.getUsername()); - participant.setUserId(importAccount.getUsername()); - userEntity = new UserEntity(); - userEntity.setBaseUrl(importAccount.getBaseUrl()); - userItems.add(new AdvancedUserItem(participant, userEntity, account)); - } - - adapter.addListener(onImportItemClickListener); - adapter.updateDataSet(userItems, false); - } + prepareViews(); } - prepareViews(); - } + private void prepareViews() { + LinearLayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity()); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHasFixedSize(true); + recyclerView.setAdapter(adapter); - private void prepareViews() { - LinearLayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity()); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setHasFixedSize(true); - recyclerView.setAdapter(adapter); + swipeRefreshLayout.setEnabled(false); + } - swipeRefreshLayout.setEnabled(false); - } + private void reauthorizeFromImport(Account account) { + ImportAccount importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account); + Bundle bundle = new Bundle(); + bundle.putString(BundleKeys.INSTANCE.getKEY_BASE_URL(), importAccount.baseUrl); + bundle.putString(BundleKeys.INSTANCE.getKEY_USERNAME(), importAccount.username); + bundle.putString(BundleKeys.INSTANCE.getKEY_TOKEN(), importAccount.token); + bundle.putBoolean(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT(), true); + getRouter().pushController(RouterTransaction.with(new AccountVerificationController(bundle)) + .pushChangeHandler(new HorizontalChangeHandler()) + .popChangeHandler(new HorizontalChangeHandler())); + } - private void reauthorizeFromImport(Account account) { - ImportAccount importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account); - Bundle bundle = new Bundle(); - bundle.putString(BundleKeys.INSTANCE.getKEY_BASE_URL(), importAccount.getBaseUrl()); - bundle.putString(BundleKeys.INSTANCE.getKEY_USERNAME(), importAccount.getUsername()); - bundle.putString(BundleKeys.INSTANCE.getKEY_TOKEN(), importAccount.getToken()); - bundle.putBoolean(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT(), true); - getRouter().pushController(RouterTransaction.with(new AccountVerificationController(bundle)) - .pushChangeHandler(new HorizontalChangeHandler()) - .popChangeHandler(new HorizontalChangeHandler())); - } - - @Override - public String getTitle() { - return getResources().getString(R.string.nc_select_an_account); - } + @Override + public String getTitle() { + return getResources().getString(R.string.nc_select_an_account); + } } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.java b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.java index bc0cc10af..a06905cf8 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.java @@ -35,7 +35,6 @@ import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.ImageView; import androidx.annotation.NonNull; -import autodagger.AutoInjector; import butterknife.BindView; import butterknife.OnClick; import com.bluelinelabs.conductor.RouterTransaction; @@ -57,11 +56,9 @@ import com.vanniktech.emoji.emoji.Emoji; import com.vanniktech.emoji.listeners.OnEmojiClickListener; import com.vanniktech.emoji.listeners.OnEmojiPopupDismissListener; import com.vanniktech.emoji.listeners.OnEmojiPopupShownListener; -import javax.inject.Inject; import org.greenrobot.eventbus.EventBus; import org.parceler.Parcels; -@AutoInjector(NextcloudTalkApplication.class) public class EntryMenuController extends BaseController { @BindView(R.id.ok_button) @@ -76,12 +73,6 @@ public class EntryMenuController extends BaseController { @BindView(R.id.smileyButton) ImageView smileyButton; - @Inject - EventBus eventBus; - - @Inject - UserUtils userUtils; - private int operationCode; private Conversation conversation; private Intent shareIntent; diff --git a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java index 0554c5948..d12378a00 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java @@ -73,7 +73,6 @@ import org.greenrobot.eventbus.EventBus; import org.parceler.Parcels; import retrofit2.HttpException; -@AutoInjector(NextcloudTalkApplication.class) public class OperationsMenuController extends BaseController { @BindView(R.id.progress_bar) @@ -160,10 +159,6 @@ public class OperationsMenuController extends BaseController { @Override protected void onViewBound(@NonNull View view) { super.onViewBound(view); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - processOperation(); } diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/BusModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/BusModule.java deleted file mode 100644 index a582ec4aa..000000000 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/BusModule.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.dagger.modules; - -import dagger.Module; -import dagger.Provides; -import javax.inject.Singleton; -import org.greenrobot.eventbus.EventBus; - -@Module -public class BusModule { - - @Provides - @Singleton - public EventBus provideEventBus() { - return EventBus.getDefault(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/ContextModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/ContextModule.java deleted file mode 100644 index e56558335..000000000 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/ContextModule.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.dagger.modules; - -import android.content.Context; -import androidx.annotation.NonNull; -import dagger.Module; -import dagger.Provides; - -@Module -public class ContextModule { - private final Context context; - - public ContextModule(@NonNull final Context context) { - this.context = context; - } - - @Provides - public Context provideContext() { - return context; - } -} diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java deleted file mode 100644 index 0121bd77b..000000000 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.dagger.modules; - -import android.content.Context; -import androidx.annotation.NonNull; -import com.nextcloud.talk.R; -import com.nextcloud.talk.models.database.Models; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import dagger.Module; -import dagger.Provides; -import io.requery.Persistable; -import io.requery.android.sqlcipher.SqlCipherDatabaseSource; -import io.requery.reactivex.ReactiveEntityStore; -import io.requery.reactivex.ReactiveSupport; -import io.requery.sql.Configuration; -import io.requery.sql.EntityDataStore; -import javax.inject.Singleton; -import net.orange_box.storebox.StoreBox; - -@Module -public class DatabaseModule { - - @Provides - @Singleton - public SqlCipherDatabaseSource provideSqlCipherDatabaseSource(@NonNull final Context context) { - return new SqlCipherDatabaseSource(context, Models.DEFAULT, - context.getResources().getString(R.string.nc_app_name).toLowerCase() - .replace(" ", "_").trim() + ".sqlite", - context.getString(R.string.nc_talk_database_encryption_key), 6); - } - - @Provides - @Singleton - public ReactiveEntityStore provideDataStore( - @NonNull final SqlCipherDatabaseSource sqlCipherDatabaseSource) { - final Configuration configuration = sqlCipherDatabaseSource.getConfiguration(); - return ReactiveSupport.toReactiveStore(new EntityDataStore(configuration)); - } - - @Provides - @Singleton - public AppPreferences providePreferences(@NonNull final Context poContext) { - return StoreBox.create(poContext, AppPreferences.class); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java deleted file mode 100644 index 5f99ee588..000000000 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.dagger.modules; - -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.github.aurae.retrofit2.LoganSquareConverterFactory; -import com.nextcloud.talk.BuildConfig; -import com.nextcloud.talk.R; -import com.nextcloud.talk.api.NcApi; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.LoggingUtils; -import com.nextcloud.talk.utils.database.user.UserUtils; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.nextcloud.talk.utils.singletons.AvatarStatusCodeHolder; -import com.nextcloud.talk.utils.ssl.MagicKeyManager; -import com.nextcloud.talk.utils.ssl.MagicTrustManager; -import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat; -import dagger.Module; -import dagger.Provides; -import io.reactivex.schedulers.Schedulers; -import java.io.IOException; -import java.net.CookieManager; -import java.net.CookiePolicy; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.util.concurrent.TimeUnit; -import javax.inject.Singleton; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.X509KeyManager; -import okhttp3.Authenticator; -import okhttp3.Cache; -import okhttp3.Credentials; -import okhttp3.Dispatcher; -import okhttp3.Interceptor; -import okhttp3.JavaNetCookieJar; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.Route; -import okhttp3.internal.tls.OkHostnameVerifier; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; - -@Module(includes = DatabaseModule.class) -public class RestModule { - - private static final String TAG = "RestModule"; - private final Context context; - - public RestModule(Context context) { - this.context = context; - } - - @Singleton - @Provides - NcApi provideNcApi(Retrofit retrofit) { - return retrofit.create(NcApi.class); - } - - @Singleton - @Provides - Proxy provideProxy(AppPreferences appPreferences) { - if (!TextUtils.isEmpty(appPreferences.getProxyType()) && !"No proxy".equals( - appPreferences.getProxyType()) - && !TextUtils.isEmpty(appPreferences.getProxyHost())) { - GetProxyRunnable getProxyRunnable = new GetProxyRunnable(appPreferences); - Thread getProxyThread = new Thread(getProxyRunnable); - getProxyThread.start(); - try { - getProxyThread.join(); - return getProxyRunnable.getProxyValue(); - } catch (InterruptedException e) { - Log.e(TAG, "Failed to join the thread while getting proxy: " + e.getLocalizedMessage()); - return Proxy.NO_PROXY; - } - } else { - return Proxy.NO_PROXY; - } - } - - @Singleton - @Provides - Retrofit provideRetrofit(OkHttpClient httpClient) { - Retrofit.Builder retrofitBuilder = new Retrofit.Builder() - .client(httpClient) - .baseUrl("https://nextcloud.com") - .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) - .addConverterFactory(LoganSquareConverterFactory.create()); - - return retrofitBuilder.build(); - } - - @Singleton - @Provides - MagicTrustManager provideMagicTrustManager() { - return new MagicTrustManager(); - } - - @Singleton - @Provides - MagicKeyManager provideKeyManager(AppPreferences appPreferences, UserUtils userUtils) { - KeyStore keyStore = null; - try { - keyStore = KeyStore.getInstance("AndroidKeyStore"); - keyStore.load(null); - KeyManagerFactory kmf = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, null); - X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0]; - return new MagicKeyManager(origKm, userUtils, appPreferences); - } catch (KeyStoreException e) { - Log.e(TAG, "KeyStoreException " + e.getLocalizedMessage()); - } catch (CertificateException e) { - Log.e(TAG, "CertificateException " + e.getLocalizedMessage()); - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "NoSuchAlgorithmException " + e.getLocalizedMessage()); - } catch (IOException e) { - Log.e(TAG, "IOException " + e.getLocalizedMessage()); - } catch (UnrecoverableKeyException e) { - Log.e(TAG, "UnrecoverableKeyException " + e.getLocalizedMessage()); - } - - return null; - } - - @Singleton - @Provides - SSLSocketFactoryCompat provideSslSocketFactoryCompat(MagicKeyManager keyManager, MagicTrustManager - magicTrustManager) { - return new SSLSocketFactoryCompat(keyManager, magicTrustManager); - } - - @Singleton - @Provides - CookieManager provideCookieManager() { - CookieManager cookieManager = new CookieManager(); - cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); - return cookieManager; - } - - @Singleton - @Provides - Cache provideCache() { - return new Cache(NextcloudTalkApplication.Companion.getSharedApplication().getCacheDir(), - Long.MAX_VALUE); - } - - @Singleton - @Provides - Dispatcher provideDispatcher() { - Dispatcher dispatcher = new Dispatcher(); - dispatcher.setMaxRequestsPerHost(100); - dispatcher.setMaxRequests(100); - return dispatcher; - } - - @Singleton - @Provides - OkHttpClient provideHttpClient(Proxy proxy, AppPreferences appPreferences, - MagicTrustManager magicTrustManager, - SSLSocketFactoryCompat sslSocketFactoryCompat, Cache cache, - CookieManager cookieManager, Dispatcher dispatcher) { - OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); - - httpClient.retryOnConnectionFailure(true); - httpClient.connectTimeout(45, TimeUnit.SECONDS); - httpClient.readTimeout(45, TimeUnit.SECONDS); - httpClient.writeTimeout(45, TimeUnit.SECONDS); - - httpClient.cookieJar(new JavaNetCookieJar(cookieManager)); - httpClient.cache(cache); - - // Trust own CA and all self-signed certs - httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager); - httpClient.retryOnConnectionFailure(true); - httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE)); - - httpClient.dispatcher(dispatcher); - if (!Proxy.NO_PROXY.equals(proxy)) { - httpClient.proxy(proxy); - - if (appPreferences.getProxyCredentials() && - !TextUtils.isEmpty(appPreferences.getProxyUsername()) && - !TextUtils.isEmpty(appPreferences.getProxyPassword())) { - httpClient.proxyAuthenticator(new MagicAuthenticator(Credentials.basic( - appPreferences.getProxyUsername(), - appPreferences.getProxyPassword()), "Proxy-Authorization")); - } - } - - httpClient.addInterceptor(new HeadersInterceptor()); - - if (BuildConfig.DEBUG && !context.getResources().getBoolean(R.bool.nc_is_debug)) { - HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); - loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - loggingInterceptor.redactHeader("Authorization"); - loggingInterceptor.redactHeader("Proxy-Authorization"); - httpClient.addInterceptor(loggingInterceptor); - } else if (context.getResources().getBoolean(R.bool.nc_is_debug)) { - - HttpLoggingInterceptor.Logger fileLogger = - s -> LoggingUtils.INSTANCE.writeLogEntryToFile(context, s); - HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(fileLogger); - loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - loggingInterceptor.redactHeader("Authorization"); - loggingInterceptor.redactHeader("Proxy-Authorization"); - httpClient.addInterceptor(loggingInterceptor); - } - - return httpClient.build(); - } - - public static class HeadersInterceptor implements Interceptor { - - @NonNull - @Override - public Response intercept(@NonNull Chain chain) throws IOException { - Request original = chain.request(); - Request request = original.newBuilder() - .header("User-Agent", ApiUtils.getUserAgent()) - .header("Accept", "application/json") - .header("OCS-APIRequest", "true") - .method(original.method(), original.body()) - .build(); - - Response response = chain.proceed(request); - - if (request.url().encodedPath().contains("/avatar/")) { - AvatarStatusCodeHolder.getInstance().setStatusCode(response.code()); - } - - return response; - } - } - - public static class MagicAuthenticator implements Authenticator { - - private String credentials; - private String authenticatorType; - - public MagicAuthenticator(@NonNull String credentials, @NonNull String authenticatorType) { - this.credentials = credentials; - this.authenticatorType = authenticatorType; - } - - @Nullable - @Override - public Request authenticate(@Nullable Route route, @NonNull Response response) { - if (response.request().header(authenticatorType) != null) { - return null; - } - - Response countedResponse = response; - - int attemptsCount = 0; - - while ((countedResponse = countedResponse.priorResponse()) != null) { - attemptsCount++; - if (attemptsCount == 3) { - return null; - } - } - - return response.request().newBuilder() - .header(authenticatorType, credentials) - .build(); - } - } - - private class GetProxyRunnable implements Runnable { - private volatile Proxy proxy; - private AppPreferences appPreferences; - - GetProxyRunnable(AppPreferences appPreferences) { - this.appPreferences = appPreferences; - } - - @Override - public void run() { - if (Proxy.Type.SOCKS.equals(Proxy.Type.valueOf(appPreferences.getProxyType()))) { - proxy = new Proxy(Proxy.Type.valueOf(appPreferences.getProxyType()), - InetSocketAddress.createUnresolved(appPreferences.getProxyHost(), Integer.parseInt( - appPreferences.getProxyPort()))); - } else { - proxy = new Proxy(Proxy.Type.valueOf(appPreferences.getProxyType()), - new InetSocketAddress(appPreferences.getProxyHost(), - Integer.parseInt(appPreferences.getProxyPort()))); - } - } - - Proxy getProxyValue() { - return proxy; - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java deleted file mode 100644 index 7dcceec43..000000000 --- a/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.jobs; - -import android.app.NotificationManager; -import android.content.Context; -import android.os.Build; -import android.text.TextUtils; -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.work.Worker; -import androidx.work.WorkerParameters; -import autodagger.AutoInjector; -import com.bluelinelabs.logansquare.LoganSquare; -import com.nextcloud.talk.R; -import com.nextcloud.talk.api.NcApi; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.models.json.generic.GenericOverall; -import com.nextcloud.talk.models.json.push.PushConfigurationState; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils; -import com.nextcloud.talk.utils.database.user.UserUtils; -import com.nextcloud.talk.webrtc.WebSocketConnectionHelper; -import io.reactivex.CompletableObserver; -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; -import java.io.IOException; -import java.net.CookieManager; -import java.util.HashMap; -import java.util.zip.CRC32; -import javax.inject.Inject; -import okhttp3.JavaNetCookieJar; -import okhttp3.OkHttpClient; -import retrofit2.Retrofit; - -@AutoInjector(NextcloudTalkApplication.class) -public class AccountRemovalWorker extends Worker { - public static final String TAG = "AccountRemovalWorker"; - - @Inject - UserUtils userUtils; - - @Inject - ArbitraryStorageUtils arbitraryStorageUtils; - - @Inject - Retrofit retrofit; - - @Inject - OkHttpClient okHttpClient; - - NcApi ncApi; - - public AccountRemovalWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { - super(context, workerParams); - } - - @NonNull - @Override - public Result doWork() { - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - - PushConfigurationState pushConfigurationState; - String credentials; - for (Object userEntityObject : userUtils.getUsersScheduledForDeletion()) { - UserEntity userEntity = (UserEntity) userEntityObject; - try { - if (!TextUtils.isEmpty(userEntity.getPushConfigurationState())) { - pushConfigurationState = LoganSquare.parse(userEntity.getPushConfigurationState(), - PushConfigurationState.class); - PushConfigurationState finalPushConfigurationState = pushConfigurationState; - - credentials = ApiUtils.getCredentials(userEntity.getUsername(), userEntity.getToken()); - - ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(new - JavaNetCookieJar(new CookieManager())).build()).build().create(NcApi.class); - - String finalCredentials = credentials; - ncApi.unregisterDeviceForNotificationsWithNextcloud(credentials, - ApiUtils.getUrlNextcloudPush(userEntity - .getBaseUrl())) - .blockingSubscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onNext(GenericOverall genericOverall) { - if (genericOverall.getOcs().getMeta().getStatusCode() == 200 - || genericOverall.getOcs().getMeta().getStatusCode() == 202) { - HashMap queryMap = new HashMap<>(); - queryMap.put("deviceIdentifier", - finalPushConfigurationState.getDeviceIdentifier()); - queryMap.put("userPublicKey", finalPushConfigurationState.getUserPublicKey()); - queryMap.put("deviceIdentifierSignature", - finalPushConfigurationState.getDeviceIdentifierSignature()); - - ncApi.unregisterDeviceForNotificationsWithProxy - (ApiUtils.getUrlPushProxy(), queryMap) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onNext(Void aVoid) { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - String groupName = - String.format(getApplicationContext().getResources() - .getString(R.string - .nc_notification_channel), userEntity.getUserId(), - userEntity.getBaseUrl()); - CRC32 crc32 = new CRC32(); - crc32.update(groupName.getBytes()); - NotificationManager notificationManager = - (NotificationManager) getApplicationContext().getSystemService - (Context.NOTIFICATION_SERVICE); - - if (notificationManager != null) { - notificationManager.deleteNotificationChannelGroup(Long - .toString(crc32.getValue())); - } - } - - WebSocketConnectionHelper.deleteExternalSignalingInstanceForUserEntity( - userEntity.getId()); - - arbitraryStorageUtils.deleteAllEntriesForAccountIdentifier( - userEntity.getId()).subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onNext(Object o) { - userUtils.deleteUser(userEntity.getId()) - .subscribe(new CompletableObserver() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onComplete() { - - } - - @Override - public void onError(Throwable e) { - - } - }); - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - - } - }); - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - - } - }); - } - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - - } - }); - } else { - userUtils.deleteUser(userEntity.getId()) - .subscribe(new CompletableObserver() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onComplete() { - - } - - @Override - public void onError(Throwable e) { - - } - }); - } - } catch (IOException e) { - Log.d(TAG, "Something went wrong while removing job at parsing PushConfigurationState"); - userUtils.deleteUser(userEntity.getId()) - .subscribe(new CompletableObserver() { - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public void onComplete() { - - } - - @Override - public void onError(Throwable e) { - - } - }); - } - } - - return Result.success(); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.kt new file mode 100644 index 000000000..0842725cf --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.kt @@ -0,0 +1,137 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.jobs + +import android.app.NotificationManager +import android.content.Context +import android.os.Build +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.nextcloud.talk.R +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.local.dao.UsersDao +import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.newarch.local.models.getCredentials +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils +import com.nextcloud.talk.webrtc.WebSocketConnectionHelper.Companion.deleteExternalSignalingInstanceForUserEntity +import io.reactivex.Observer +import io.reactivex.disposables.Disposable +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import okhttp3.JavaNetCookieJar +import okhttp3.OkHttpClient +import org.koin.core.KoinComponent +import org.koin.core.inject +import retrofit2.Retrofit +import java.net.CookieManager +import java.util.* +import java.util.zip.CRC32 + +class AccountRemovalWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent { + val arbitraryStorageUtils: ArbitraryStorageUtils by inject() + val okHttpClient: OkHttpClient by inject() + val retrofit: Retrofit by inject() + private val usersDao: UsersDao by inject() + val usersRepository: UsersRepository by inject() + var ncApi: NcApi? = null + + override suspend fun doWork(): Result { + val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + for (userEntityObject in usersDao.getUsersScheduledForDeletion()) { + val userEntity: UserNgEntity = userEntityObject + val credentials = userEntity.getCredentials() + userEntity.pushConfiguration?.let { + ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build().create(NcApi::class.java) + ncApi!!.unregisterDeviceForNotificationsWithNextcloud(credentials, + ApiUtils.getUrlNextcloudPush(userEntity.baseUrl)) + .blockingSubscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + override fun onNext(genericOverall: GenericOverall) { + if (genericOverall.ocs.meta.statusCode == 200 + || genericOverall.ocs.meta.statusCode == 202) { + val queryMap = HashMap() + queryMap["deviceIdentifier"] = userEntity.pushConfiguration!!.deviceIdentifier + queryMap["userPublicKey"] = userEntity.pushConfiguration!!.userPublicKey + queryMap["deviceIdentifierSignature"] = userEntity.pushConfiguration!!.deviceIdentifierSignature + + ncApi!!.unregisterDeviceForNotificationsWithProxy(ApiUtils.getUrlPushProxy(), queryMap) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + override fun onNext(aVoid: Void) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val groupName = java.lang.String.format(applicationContext.resources + .getString(R.string.nc_notification_channel), userEntity.userId, + userEntity.baseUrl) + val crc32 = CRC32() + crc32.update(groupName.toByteArray()) + notificationManager.deleteNotificationChannelGroup(java.lang.Long + .toString(crc32.value)) + } + deleteExternalSignalingInstanceForUserEntity( + userEntity.id!!) + arbitraryStorageUtils!!.deleteAllEntriesForAccountIdentifier( + userEntity.id!!).subscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + override fun onNext(o: Any) { + GlobalScope.launch { + val job = async { + usersRepository.deleteUserWithId(userEntity.id!!) + } + job.await() + } + } + + override fun onError(e: Throwable) {} + override fun onComplete() {} + }) + } + + override fun onError(e: Throwable) {} + override fun onComplete() {} + }) + } + } + + override fun onError(e: Throwable) {} + override fun onComplete() {} + }) + }?: run { + GlobalScope.launch { + val job = async { + usersRepository.deleteUserWithId(userEntity.id!!) + } + job.await() + } + } + } + return Result.success() + } + + companion object { + const val TAG = "AccountRemovalWorker" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.kt b/app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.kt index f29724951..fdaaddcdc 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/AddParticipantsToConversation.kt @@ -23,7 +23,6 @@ import android.content.Context import androidx.work.CoroutineWorker import androidx.work.Worker import androidx.work.WorkerParameters -import autodagger.AutoInjector import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication @@ -35,20 +34,14 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SELECTED_GROUPS import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SELECTED_USERS import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN -import com.nextcloud.talk.utils.database.user.UserUtils import io.reactivex.schedulers.Schedulers import org.greenrobot.eventbus.EventBus import org.koin.core.KoinComponent import org.koin.core.inject -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class AddParticipantsToConversation(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent { - @JvmField - @Inject - var ncApi: NcApi? = null - + val ncApi: NcApi by inject() val eventBus: EventBus by inject() val usersRepository: UsersRepository by inject() @@ -63,24 +56,18 @@ class AddParticipantsToConversation(context: Context, for (userId in selectedUserIds!!) { retrofitBucket = ApiUtils.getRetrofitBucketForAddParticipant(user.baseUrl, conversationToken, userId) - ncApi!!.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap) + ncApi.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap) .subscribeOn(Schedulers.io()) .blockingSubscribe() } for (groupId in selectedGroupIds!!) { retrofitBucket = ApiUtils.getRetrofitBucketForAddGroupParticipant(user.baseUrl, conversationToken, groupId) - ncApi!!.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap) + ncApi.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap) .subscribeOn(Schedulers.io()) .blockingSubscribe() } eventBus.post(EventStatus(user.id!!, EventStatus.EventType.PARTICIPANTS_UPDATE, true)) return Result.success() } - - init { - sharedApplication - ?.componentApplication - ?.inject(this) - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/nextcloud/talk/jobs/CapabilitiesWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/CapabilitiesWorker.kt index 19868b1e8..69276fc0e 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/CapabilitiesWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/CapabilitiesWorker.kt @@ -55,7 +55,7 @@ class CapabilitiesWorker(context: Context, workerParams: WorkerParameters) : Cor internalUserEntity.capabilities = capabilitiesOverall.ocs.data.capabilities runBlocking { val result = usersRepository.updateUser(internalUserEntity) - eventBus!!.post(EventStatus(internalUserEntity.id!!, + eventBus.post(EventStatus(internalUserEntity.id!!, EventStatus.EventType.CAPABILITIES_FETCH, result > 0)) } @@ -64,7 +64,7 @@ class CapabilitiesWorker(context: Context, workerParams: WorkerParameters) : Cor override suspend fun doWork(): Result { val data = inputData val internalUserId = data.getLong(KEY_INTERNAL_USER_ID, -1) - var userEntity: UserNgEntity? + val userEntity: UserNgEntity? var userEntityObjectList: MutableList = ArrayList() if (internalUserId == -1L || usersRepository.getUserWithId(internalUserId) == null) { userEntityObjectList = usersRepository.getUsers().toMutableList() @@ -74,20 +74,19 @@ class CapabilitiesWorker(context: Context, workerParams: WorkerParameters) : Cor } for (userEntityObject in userEntityObjectList) { - val internalUserEntity = userEntityObject ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build().create(NcApi::class.java) - ncApi!!.getCapabilities(ApiUtils.getCredentials(internalUserEntity.username, - internalUserEntity.token), - ApiUtils.getUrlForCapabilities(internalUserEntity.baseUrl)) + ncApi!!.getCapabilities(ApiUtils.getCredentials(userEntityObject.username, + userEntityObject.token), + ApiUtils.getUrlForCapabilities(userEntityObject.baseUrl)) .retry(3) .blockingSubscribe(object : Observer { override fun onSubscribe(d: Disposable) {} override fun onNext(capabilitiesOverall: CapabilitiesOverall) { - updateUser(capabilitiesOverall, internalUserEntity) + updateUser(capabilitiesOverall, userEntityObject) } override fun onError(e: Throwable) { - eventBus.post(EventStatus(internalUserEntity.id!!, + eventBus.post(EventStatus(userEntityObject.id!!, EventStatus.EventType.CAPABILITIES_FETCH, false)) } diff --git a/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.java deleted file mode 100644 index e7049cecb..000000000 --- a/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.jobs; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.Data; -import androidx.work.Worker; -import androidx.work.WorkerParameters; -import autodagger.AutoInjector; -import com.nextcloud.talk.api.NcApi; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.events.EventStatus; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.models.json.generic.GenericOverall; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.bundle.BundleKeys; -import com.nextcloud.talk.utils.database.user.UserUtils; -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import java.net.CookieManager; -import javax.inject.Inject; -import okhttp3.JavaNetCookieJar; -import okhttp3.OkHttpClient; -import org.greenrobot.eventbus.EventBus; -import retrofit2.Retrofit; - -@AutoInjector(NextcloudTalkApplication.class) -public class DeleteConversationWorker extends Worker { - @Inject - Retrofit retrofit; - - @Inject - OkHttpClient okHttpClient; - - @Inject - UserUtils userUtils; - - @Inject - EventBus eventBus; - - NcApi ncApi; - - public DeleteConversationWorker(@NonNull Context context, - @NonNull WorkerParameters workerParams) { - super(context, workerParams); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - } - - @NonNull - @Override - public Result doWork() { - Data data = getInputData(); - long operationUserId = data.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), -1); - String conversationToken = data.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN()); - UserEntity operationUser = userUtils.getUserWithId(operationUserId); - - if (operationUser != null) { - String credentials = - ApiUtils.getCredentials(operationUser.getUsername(), operationUser.getToken()); - ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(new - JavaNetCookieJar(new CookieManager())).build()).build().create(NcApi.class); - - EventStatus eventStatus = new EventStatus(operationUser.getId(), - EventStatus.EventType.CONVERSATION_UPDATE, true); - - ncApi.deleteRoom(credentials, ApiUtils.getRoom(operationUser.getBaseUrl(), conversationToken)) - .subscribeOn(Schedulers.io()) - .blockingSubscribe(new Observer() { - Disposable disposable; - - @Override - public void onSubscribe(Disposable d) { - disposable = d; - } - - @Override - public void onNext(GenericOverall genericOverall) { - eventBus.postSticky(eventStatus); - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - disposable.dispose(); - } - }); - } - - return Result.success(); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.kt new file mode 100644 index 000000000..f531d50c5 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/jobs/DeleteConversationWorker.kt @@ -0,0 +1,93 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.jobs + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import autodagger.AutoInjector +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.events.EventStatus +import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.newarch.local.models.getCredentials +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import io.reactivex.Observer +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import okhttp3.JavaNetCookieJar +import okhttp3.OkHttpClient +import org.greenrobot.eventbus.EventBus +import org.koin.core.KoinComponent +import org.koin.core.inject +import retrofit2.Retrofit +import java.net.CookieManager + +@AutoInjector(NextcloudTalkApplication::class) +class DeleteConversationWorker(context: Context, + workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent { + val retrofit: Retrofit by inject() + val okHttpClient: OkHttpClient by inject() + val eventBus: EventBus by inject() + val usersRepository: UsersRepository by inject() + + var ncApi: NcApi? = null + override suspend fun doWork(): Result { + val data = inputData + val operationUserId = data.getLong(KEY_INTERNAL_USER_ID, -1) + val conversationToken = data.getString(KEY_ROOM_TOKEN) + val operationUser: UserNgEntity? = usersRepository.getUserWithId(operationUserId) + operationUser?.let { + val credentials = it.getCredentials() + ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build().create(NcApi::class.java) + val eventStatus = EventStatus(it.id!!, + EventStatus.EventType.CONVERSATION_UPDATE, true) + ncApi!!.deleteRoom(credentials, ApiUtils.getRoom(it.baseUrl, conversationToken)) + .subscribeOn(Schedulers.io()) + .blockingSubscribe(object : Observer { + var disposable: Disposable? = null + override fun onSubscribe(d: Disposable) { + disposable = d + } + + override fun onNext(genericOverall: GenericOverall) { + eventBus!!.postSticky(eventStatus) + } + + override fun onError(e: Throwable) {} + override fun onComplete() { + disposable!!.dispose() + } + }) + } + return Result.success() + } + + init { + sharedApplication + ?.componentApplication + ?.inject(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index ab60f816e..926104821 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -50,7 +50,6 @@ import androidx.emoji.text.EmojiCompat import androidx.work.ListenableWorker.Result import androidx.work.Worker import androidx.work.WorkerParameters -import autodagger.AutoInjector import coil.Coil import coil.target.Target import coil.transform.CircleCropTransformation @@ -122,9 +121,7 @@ import java.util.function.Consumer import java.util.zip.CRC32 import javax.crypto.Cipher import javax.crypto.NoSuchPaddingException -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class NotificationWorker( context: Context, workerParams: WorkerParameters @@ -134,10 +131,8 @@ class NotificationWorker( val retrofit: Retrofit by inject() val okHttpClient: OkHttpClient by inject() val usersRepository: UsersRepository by inject() + val arbitraryStorageUtils: ArbitraryStorageUtils by inject() - @JvmField - @Inject - var arbitraryStorageUtils: ArbitraryStorageUtils? = null private var ncApi: NcApi? = null private var decryptedPushMessage: DecryptedPushMessage? = null private var context: Context? = null @@ -621,7 +616,6 @@ class NotificationWorker( } override fun doWork(): Result { - sharedApplication!!.componentApplication.inject(this) context = applicationContext val data = inputData val subject = diff --git a/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt index 464787537..9830a2971 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt @@ -22,10 +22,7 @@ package com.nextcloud.talk.jobs import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters -import autodagger.AutoInjector import com.nextcloud.talk.api.NcApi -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.getCredentials @@ -38,17 +35,13 @@ import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import org.koin.core.KoinComponent import org.koin.core.inject -import java.util.Collections -import javax.inject.Inject +import java.util.* -@AutoInjector(NextcloudTalkApplication::class) class ShareOperationWorker( context: Context, workerParams: WorkerParameters ) : Worker(context, workerParams), KoinComponent { - @JvmField @Inject - var ncApi: NcApi? = null - + val ncApi: NcApi by inject() val usersRepository: UsersRepository by inject() private val userId: Long @@ -79,9 +72,6 @@ class ShareOperationWorker( } init { - sharedApplication - ?.componentApplication - ?.inject(this) val data = workerParams.inputData userId = data.getLong(KEY_INTERNAL_USER_ID, 0) roomToken = data.getString(KEY_ROOM_TOKEN) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/SignalingSettingsWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/SignalingSettingsWorker.kt index ae8c3b4c4..fd8d6371d 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/SignalingSettingsWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/SignalingSettingsWorker.kt @@ -46,17 +46,12 @@ import org.koin.core.inject import java.util.* import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class SignalingSettingsWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent { - @JvmField @Inject - var ncApi: NcApi? = null + val ncApi: NcApi by inject() val eventBus: EventBus by inject() val usersRepository: UsersRepository by inject() override suspend fun doWork(): Result { - sharedApplication - ?.componentApplication - ?.inject(this) val data = inputData val internalUserId = data.getLong(KEY_INTERNAL_USER_ID, -1) var userEntityList: MutableList = ArrayList() diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/DataChannelMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/signaling/DataChannelMessage.java index 59c18313e..f5be2a3b9 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/signaling/DataChannelMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/DataChannelMessage.java @@ -30,11 +30,11 @@ import org.parceler.ParcelPropertyConverter; @JsonObject public class DataChannelMessage { @JsonField(name = "type") - String type; + public String type; @ParcelPropertyConverter(ObjectParcelConverter.class) @JsonField(name = "payload") - Object payload; + public Object payload; public DataChannelMessage(String type) { this.type = type; diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/ConversationsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/ConversationsRepositoryImpl.kt index 0bf9f4d19..7df23df4a 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/ConversationsRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/ConversationsRepositoryImpl.kt @@ -68,6 +68,15 @@ class ConversationsRepositoryImpl(val conversationsDao: ConversationsDao) : ) } + override suspend fun getConversationForUserWithToken(userId: Long, token: String): Conversation? { + val conversationEntity = conversationsDao.getConversationForUserWithToken(userId, token) + if (conversationEntity != null) { + return conversationEntity.toConversation() + } + + return null + } + override suspend fun clearConversationsForUser(userId: Long) { conversationsDao .clearConversationsForUser(userId) diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt index fd731b9c2..9e2b8de99 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt @@ -20,13 +20,10 @@ package com.nextcloud.talk.newarch.data.source.remote +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall import com.nextcloud.talk.models.json.generic.GenericOverall -import retrofit2.http.DELETE -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.POST -import retrofit2.http.Url +import retrofit2.http.* interface ApiService { @@ -64,4 +61,6 @@ interface ApiService { @Url url: String ): GenericOverall -} \ No newline at end of file + @GET + suspend fun getRoom(@Header("Authorization") authorization: String, @Url url: String): RoomOverall +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt index 3eaf83918..b19fa208d 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt @@ -30,10 +30,12 @@ import coil.decode.SvgDecoder import com.github.aurae.retrofit2.LoganSquareConverterFactory import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R +import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.newarch.data.repository.online.NextcloudTalkRepositoryImpl import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler import com.nextcloud.talk.newarch.data.source.remote.ApiService +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository import com.nextcloud.talk.newarch.utils.NetworkUtils import com.nextcloud.talk.newarch.utils.NetworkUtils.GetProxyRunnable @@ -73,6 +75,7 @@ import javax.net.ssl.X509KeyManager val NetworkModule = module { single { createService(get()) } + single { createLegacyNcApi(get()) } single { createRetrofit(get()) } single { createProxy(get()) } single { createTrustManager() } @@ -189,7 +192,7 @@ fun createSslSocketFactory( fun createKeyManager( appPreferences: AppPreferences, - userUtils: UserUtils + usersRepository: UsersRepository ): MagicKeyManager? { val keyStore: KeyStore? try { @@ -198,7 +201,7 @@ fun createKeyManager( val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) kmf.init(keyStore, null) val origKm = kmf.keyManagers[0] as X509KeyManager - return MagicKeyManager(origKm, userUtils, appPreferences) + return MagicKeyManager(origKm, usersRepository, appPreferences) } catch (e: KeyStoreException) { Log.e("NetworkModule", "KeyStoreException " + e.localizedMessage!!) } catch (e: CertificateException) { @@ -255,6 +258,10 @@ fun createService(retrofit: Retrofit): ApiService { return retrofit.create(ApiService::class.java) } +fun createLegacyNcApi(retrofit: Retrofit): NcApi { + return retrofit.create(NcApi::class.java) +} + fun createImageLoader( androidApplication: Application, okHttpClient: OkHttpClient diff --git a/app/src/main/java/com/nextcloud/talk/newarch/di/module/StorageModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/di/module/StorageModule.kt index e286d983a..220ce535b 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/di/module/StorageModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/di/module/StorageModule.kt @@ -33,6 +33,7 @@ import com.nextcloud.talk.newarch.local.dao.ConversationsDao import com.nextcloud.talk.newarch.local.dao.MessagesDao import com.nextcloud.talk.newarch.local.dao.UsersDao import com.nextcloud.talk.newarch.local.db.TalkDatabase +import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.preferences.AppPreferences import io.requery.Persistable @@ -49,10 +50,10 @@ val StorageModule = module { single { createPreferences(androidContext()) } single { createSqlCipherDatabaseSource(androidContext()) } single { createDataStore(get()) } - single { createUserUtils(get()) } single { createConversationsRepository(get()) } single { createMessagesRepository(get()) } single { createUsersRepository(get()) } + single { createArbitraryStorageUtils(get()) } single { TalkDatabase.getInstance(androidApplication()) } single { get().conversationsDao() } @@ -89,6 +90,6 @@ fun createDataStore(sqlCipherDatabaseSource: SqlCipherDatabaseSource): ReactiveE return ReactiveSupport.toReactiveStore(EntityDataStore(configuration)) } -fun createUserUtils(dataStore: ReactiveEntityStore): UserUtils { - return UserUtils(dataStore) +fun createArbitraryStorageUtils(dataStore: ReactiveEntityStore): ArbitraryStorageUtils { + return ArbitraryStorageUtils(dataStore) } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/ConversationsRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/ConversationsRepository.kt index 82c51e934..1fbb60179 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/ConversationsRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/ConversationsRepository.kt @@ -25,6 +25,7 @@ import com.nextcloud.talk.models.json.conversations.Conversation interface ConversationsRepository { fun getConversationsForUser(userId: Long): LiveData> + suspend fun getConversationForUserWithToken(userId: Long, token: String): Conversation? suspend fun clearConversationsForUser(userId: Long) suspend fun saveConversationsForUser( userId: Long, diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt index dacc1c2dd..33c17804e 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt @@ -22,6 +22,7 @@ package com.nextcloud.talk.newarch.domain.repository.online import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.newarch.local.models.UserNgEntity @@ -42,4 +43,9 @@ interface NextcloudTalkRepository { userEntity: UserNgEntity, conversation: Conversation ): GenericOverall + + suspend fun getRoomForUser( + userEntity: UserNgEntity, + roomToken: String + ): RoomOverall } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/GetRoomUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/GetRoomUseCase.kt new file mode 100644 index 000000000..a8abe65f7 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/GetRoomUseCase.kt @@ -0,0 +1,17 @@ +package com.nextcloud.talk.newarch.domain.usecases + +import com.nextcloud.talk.models.json.conversations.RoomOverall +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository +import com.nextcloud.talk.newarch.domain.usecases.base.UseCase +import org.koin.core.parameter.DefinitionParameters + +class GetRoomUseCase constructor( + private val nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler? +) : UseCase(apiErrorHandler) { + override suspend fun run(params: Any?): RoomOverall { + val definitionParameters = params as DefinitionParameters + return nextcloudTalkRepository.getRoomForUser(definitionParameters[0], definitionParameters[1]) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/LeaveConversationUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/LeaveConversationUseCase.kt index 12569ff0f..09a8e8e5c 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/LeaveConversationUseCase.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/LeaveConversationUseCase.kt @@ -34,8 +34,8 @@ class LeaveConversationUseCase constructor( override suspend fun run(params: Any?): GenericOverall { val definitionParameters = params as DefinitionParameters return nextcloudTalkRepository.leaveConversationForUser( - definitionParameters.get(0), - definitionParameters.get(1) + definitionParameters[0], + definitionParameters[1] ) } } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt new file mode 100644 index 000000000..33aa119d3 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt @@ -0,0 +1,436 @@ +package com.nextcloud.talk.newarch.features.chat + +import android.content.res.Resources +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.text.Editable +import android.text.InputFilter +import android.text.TextUtils +import android.text.TextWatcher +import android.view.* +import android.widget.AbsListView +import androidx.lifecycle.observe +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import coil.api.load +import coil.target.Target +import coil.transform.CircleCropTransformation +import com.bluelinelabs.conductor.RouterTransaction +import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler +import com.nextcloud.talk.R +import com.nextcloud.talk.adapters.messages.* +import com.nextcloud.talk.callbacks.MentionAutocompleteCallback +import com.nextcloud.talk.components.filebrowser.controllers.BrowserController +import com.nextcloud.talk.controllers.ChatController +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.models.json.mention.Mention +import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView +import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.newarch.local.models.getCredentials +import com.nextcloud.talk.newarch.local.models.maxMessageLength +import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView +import com.nextcloud.talk.newarch.utils.Images +import com.nextcloud.talk.presenters.MentionAutocompletePresenter +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DisplayUtils +import com.nextcloud.talk.utils.DrawableUtils +import com.nextcloud.talk.utils.MagicCharPolicy +import com.nextcloud.talk.utils.bundle.BundleKeys +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD +import com.nextcloud.talk.utils.text.Spans +import com.otaliastudios.autocomplete.Autocomplete +import com.stfalcon.chatkit.commons.models.IMessage +import com.stfalcon.chatkit.messages.MessageHolders +import com.stfalcon.chatkit.messages.MessagesListAdapter +import com.stfalcon.chatkit.utils.DateFormatter +import kotlinx.android.synthetic.main.controller_chat.view.* +import kotlinx.android.synthetic.main.controller_conversations_rv.view.* +import kotlinx.android.synthetic.main.view_message_input.view.* +import org.koin.android.ext.android.inject +import org.parceler.Parcels +import java.util.* +import coil.ImageLoader as CoilImageLoader +import com.stfalcon.chatkit.commons.ImageLoader as ChatKitImageLoader + +class ChatView : BaseView(), MessageHolders.ContentChecker, MessagesListAdapter.OnLoadMoreListener, MessagesListAdapter +.OnMessageLongClickListener, MessagesListAdapter.Formatter { + + lateinit var viewModel: ChatViewModel + val factory: ChatViewModelFactory by inject() + val imageLoader: CoilImageLoader by inject() + + var conversationInfoMenuItem: MenuItem? = null + var conversationVoiceCallMenuItem: MenuItem? = null + var conversationVideoMenuItem: MenuItem? = null + + private var newMessagesCount = 0 + + lateinit var recyclerViewAdapter: MessagesListAdapter + lateinit var mentionAutocomplete: Autocomplete<*> + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { + setHasOptionsMenu(true) + actionBar?.show() + viewModel = viewModelProvider(factory).get(ChatViewModel::class.java) + viewModel.init(args.getParcelable(BundleKeys.KEY_USER_ENTITY)!!, args.getString(BundleKeys.KEY_ROOM_TOKEN)!!, args.getString(KEY_CONVERSATION_PASSWORD)) + + viewModel.apply { + conversation.observe(this@ChatView) { value -> + setTitle() + setupAdapter() + + if (Conversation.ConversationType.ONE_TO_ONE_CONVERSATION == value?.type) { + loadAvatar() + } else { + actionBar?.setIcon(null) + } + } + } + return super.onCreateView(inflater, container) + } + + + override fun onAttach(view: View) { + super.onAttach(view) + setupViews() + } + + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_conversation_plus_filter, menu) + conversationInfoMenuItem = menu.findItem(R.id.conversation_info) + conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call) + conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call) + } + + private fun setupViews() { + view?.let {view -> + view.recyclerView.initRecyclerView( + LinearLayoutManager(view.context), recyclerViewAdapter, false + ) + + recyclerViewAdapter.setLoadMoreListener(this) + recyclerViewAdapter.setDateHeadersFormatter { format(it) } + recyclerViewAdapter.setOnMessageLongClickListener { onMessageLongClick(it) } + + view.popupBubbleView.setRecyclerView(view.messagesListView) + + view.popupBubbleView.setPopupBubbleListener { context -> + if (newMessagesCount != 0) { + val scrollPosition: Int + if (newMessagesCount - 1 < 0) { + scrollPosition = 0 + } else { + scrollPosition = newMessagesCount - 1 + } + view.messagesListView.postDelayed({ + view.messagesListView.smoothScrollToPosition(scrollPosition) + }, 200) + } + } + + view.messagesListView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged( + recyclerView: RecyclerView, + newState: Int + ) { + super.onScrollStateChanged(recyclerView, newState) + + if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { + if (newMessagesCount != 0) { + val layoutManager: LinearLayoutManager = view.messagesListView.layoutManager as LinearLayoutManager + if (layoutManager.findFirstCompletelyVisibleItemPosition() < + newMessagesCount + ) { + newMessagesCount = 0 + + view.popupBubbleView?.hide() + } + } + } + } + }) + + val filters = arrayOfNulls(1) + val lengthFilter = viewModel.user.maxMessageLength() + + + filters[0] = InputFilter.LengthFilter(lengthFilter) + view.messageInput.filters = filters + + view.messageInput.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence, + start: Int, + count: Int, + after: Int + ) { + + } + + override fun onTextChanged( + s: CharSequence, + start: Int, + before: Int, + count: Int + ) { + if (s.length >= lengthFilter) { + view.messageInput.error = String.format( + Objects.requireNonNull + (resources).getString(R.string.nc_limit_hit), Integer.toString(lengthFilter) + ) + } else { + view.messageInput.error = null + } + + val editable = view.messageInput.editableText + if (editable != null) { + val mentionSpans = editable.getSpans( + 0, view.messageInput.length(), + Spans.MentionChipSpan::class.java + ) + var mentionSpan: Spans.MentionChipSpan + for (i in mentionSpans.indices) { + mentionSpan = mentionSpans[i] + if (start >= editable.getSpanStart(mentionSpan) && start < editable.getSpanEnd( + mentionSpan + ) + ) { + if (editable.subSequence( + editable.getSpanStart(mentionSpan), + editable.getSpanEnd(mentionSpan) + ).toString().trim { it <= ' ' } != mentionSpan.label + ) { + editable.removeSpan(mentionSpan) + } + } + } + } + } + + override fun afterTextChanged(s: Editable) { + + } + }) + + view.messageInputView?.setAttachmentsListener { + showBrowserScreen( + BrowserController + .BrowserType.DAV_BROWSER + ) + } + + view.messageInputView?.button?.setOnClickListener { submitMessage() } + + view.messageInputView?.button?.contentDescription = resources?.getString( + R.string.nc_description_send_message_button + ) + + } + + setupMentionAutocomplete() + } + + private fun setupMentionAutocomplete() { + viewModel.conversation.value?.let { conversation -> + view?.let {view -> + val elevation = 6f + val backgroundDrawable = ColorDrawable(resources!!.getColor(R.color.bg_default)) + val presenter = MentionAutocompletePresenter(context, conversation.token) + val callback = MentionAutocompleteCallback( + activity, + viewModel.user, view.messageInput + ) + + if (!::mentionAutocomplete.isInitialized) { + mentionAutocomplete = Autocomplete.on(view.messageInput) + .with(elevation) + .with(backgroundDrawable) + .with(MagicCharPolicy('@')) + .with(presenter) + .with(callback) + .build() + } + + } + } + } + + private fun submitMessage() { + val editable = view?.messageInput?.editableText + editable?.let { + val mentionSpans = it.getSpans( + 0, it.length, + Spans.MentionChipSpan::class.java + ) + var mentionSpan: Spans.MentionChipSpan + for (i in mentionSpans.indices) { + mentionSpan = mentionSpans[i] + var mentionId = mentionSpan.id + if (mentionId.contains(" ") || mentionId.startsWith("guest/")) { + mentionId = "\"" + mentionId + "\"" + } + it.replace( + it.getSpanStart(mentionSpan), it.getSpanEnd(mentionSpan), "@$mentionId" + ) + } + + view?.messageInput?.setText("") + viewModel.sendMessage(it) + + } + } + + private fun showBrowserScreen(browserType: BrowserController.BrowserType) { + viewModel.conversation.value?.let { + val bundle = Bundle() + bundle.putParcelable( + BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap(browserType) + ) + bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap(viewModel.user)) + bundle.putString(BundleKeys.KEY_ROOM_TOKEN, it.token) + router.pushController( + RouterTransaction.with(BrowserController(bundle)) + .pushChangeHandler(VerticalChangeHandler()) + .popChangeHandler(VerticalChangeHandler()) + ) + + } + } + + private fun setupAdapter() { + val messageHolders = MessageHolders() + messageHolders.setIncomingTextConfig( + MagicIncomingTextMessageViewHolder::class.java, R.layout.item_custom_incoming_text_message + ) + messageHolders.setOutcomingTextConfig( + MagicOutcomingTextMessageViewHolder::class.java, + R.layout.item_custom_outcoming_text_message + ) + + messageHolders.setIncomingImageConfig( + MagicPreviewMessageViewHolder::class.java, R.layout.item_custom_incoming_preview_message + ) + messageHolders.setOutcomingImageConfig( + MagicPreviewMessageViewHolder::class.java, R.layout.item_custom_outcoming_preview_message + ) + + messageHolders.registerContentType( + ChatController.CONTENT_TYPE_SYSTEM_MESSAGE, MagicSystemMessageViewHolder::class.java, + R.layout.item_system_message, MagicSystemMessageViewHolder::class.java, + R.layout.item_system_message, + this + ) + + messageHolders.registerContentType( + ChatController.CONTENT_TYPE_UNREAD_NOTICE_MESSAGE, + MagicUnreadNoticeMessageViewHolder::class.java, R.layout.item_date_header, + MagicUnreadNoticeMessageViewHolder::class.java, R.layout.item_date_header, this + ) + + recyclerViewAdapter = MessagesListAdapter( + viewModel.user.userId, messageHolders, ChatKitImageLoader { imageView, url, payload -> + imageView.load(url) { + if (url!!.contains("/avatar/")) { + transformations(CircleCropTransformation()) + } else { + if (payload is ImageLoaderPayload) { + payload.map?.let { + if (payload.map.containsKey("mimetype")) { + placeholder( + DrawableUtils.getDrawableResourceIdForMimeType( + payload.map.get("mimetype") as String? + ) + ) + } + } + } + } + + if (url.startsWith(viewModel.user.baseUrl) && url.contains("index.php/core/preview?fileId=")) { + addHeader("Authorization", viewModel.user.getCredentials()) + + } + } + }) + + } + + private fun loadAvatar() { + val avatarSize = DisplayUtils.convertDpToPixel( + conversationVoiceCallMenuItem?.icon!! + .intrinsicWidth.toFloat(), activity!! + ) + .toInt() + + avatarSize.let { + val target = object : Target { + override fun onSuccess(result: Drawable) { + super.onSuccess(result) + actionBar?.setIcon(result) + } + } + + viewModel.conversation.value?.let { + val avatarRequest = Images().getRequestForUrl( + imageLoader, context, ApiUtils.getUrlForAvatarWithNameAndPixels( + viewModel.user.baseUrl, + it.name, avatarSize / 2 + ), null, target, this, + CircleCropTransformation() + ); + + imageLoader.load(avatarRequest) + + } + } + } + + override fun getLayoutId(): Int { + return R.layout.controller_chat + } + + override fun getTitle(): String? { + return viewModel.conversation.value?.displayName + } + + override fun hasContentFor(message: IMessage, type: Byte): Boolean { + when (type) { + ChatController.CONTENT_TYPE_SYSTEM_MESSAGE -> return !TextUtils.isEmpty(message.systemMessage) + ChatController.CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> return message.id == "-1" + } + + return false + } + + override fun format(date: Date): String { + return when { + DateFormatter.isToday(date) -> { + resources!!.getString(R.string.nc_date_header_today) + } + DateFormatter.isYesterday(date) -> { + resources!!.getString(R.string.nc_date_header_yesterday) + } + else -> { + DateFormatter.format(date, DateFormatter.Template.STRING_DAY_MONTH_YEAR) + } + } + } + + override fun onLoadMore(page: Int, totalItemsCount: Int) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun onMessageLongClick(message: IMessage?) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModel.kt new file mode 100644 index 000000000..fca682c01 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModel.kt @@ -0,0 +1,30 @@ +package com.nextcloud.talk.newarch.features.chat + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel +import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository +import com.nextcloud.talk.newarch.local.models.UserNgEntity +import kotlinx.coroutines.launch + +class ChatViewModel constructor(application: Application, private val conversationsRepository: ConversationsRepository) : BaseViewModel(application) { + lateinit var user: UserNgEntity + val conversation: MutableLiveData = MutableLiveData() + var conversationPassword: String? = null + + + fun init(user: UserNgEntity, conversationToken: String, conversationPassword: String?) { + viewModelScope.launch { + this@ChatViewModel.user = user + this@ChatViewModel.conversation.value = conversationsRepository.getConversationForUserWithToken(user.id!!, conversationToken) + this@ChatViewModel.conversationPassword = conversationPassword + } + } + + fun sendMessage(message: CharSequence) { + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModelFactory.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModelFactory.kt new file mode 100644 index 000000000..6de298fbd --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModelFactory.kt @@ -0,0 +1,18 @@ +package com.nextcloud.talk.newarch.features.chat + +import android.app.Application +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository + +class ChatViewModelFactory constructor( + private val application: Application, + private val conversationsRepository: ConversationsRepository +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + return ChatViewModel( + application, conversationsRepository + ) as T + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewState.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewState.kt new file mode 100644 index 000000000..f7c2c7c15 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewState.kt @@ -0,0 +1,8 @@ +package com.nextcloud.talk.newarch.features.chat + +enum class ChatViewState { + INITIAL_LOAD, + LOBBY, + CHAT, + FAILED +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt index 2c61caf06..cc1fcf926 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt @@ -26,7 +26,6 @@ import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle import android.text.InputType -import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -59,10 +58,10 @@ import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView import com.nextcloud.talk.newarch.utils.Images -import com.nextcloud.talk.newarch.utils.ViewState.FAILED -import com.nextcloud.talk.newarch.utils.ViewState.LOADED -import com.nextcloud.talk.newarch.utils.ViewState.LOADED_EMPTY -import com.nextcloud.talk.newarch.utils.ViewState.LOADING +import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.FAILED +import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.LOADED +import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.LOADED_EMPTY +import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.LOADING import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.DisplayUtils diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt index f72f4e655..e8cd8f9b4 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt @@ -42,9 +42,8 @@ import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse import com.nextcloud.talk.newarch.local.models.UserNgEntity -import com.nextcloud.talk.newarch.utils.ViewState.LOADING +import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.LOADING import com.nextcloud.talk.utils.ShareUtils -import com.nextcloud.talk.utils.database.user.UserUtils import kotlinx.coroutines.launch import org.koin.core.parameter.parametersOf diff --git a/app/src/main/java/com/nextcloud/talk/newarch/utils/ViewState.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewState.kt similarity index 88% rename from app/src/main/java/com/nextcloud/talk/newarch/utils/ViewState.kt rename to app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewState.kt index 23f996292..eb68b259f 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/utils/ViewState.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewState.kt @@ -18,9 +18,9 @@ * along with this program. If not, see . */ -package com.nextcloud.talk.newarch.utils +package com.nextcloud.talk.newarch.features.conversationsList -enum class ViewState { +enum class ConversationsListViewState { LOADING, LOADED_EMPTY, LOADED, diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt index 630e35c7e..20b58ddac 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt @@ -33,10 +33,10 @@ import com.nextcloud.talk.newarch.local.models.ConversationEntity @Dao abstract class ConversationsDao { - @Query("SELECT * FROM conversations WHERE user = :userId ORDER BY favorite DESC, last_activity DESC") + @Query("SELECT * FROM conversations WHERE user_id = :userId ORDER BY favorite DESC, last_activity DESC") abstract fun getConversationsForUser(userId: Long): LiveData> - @Query("DELETE FROM conversations WHERE user = :userId") + @Query("DELETE FROM conversations WHERE user_id = :userId") abstract suspend fun clearConversationsForUser(userId: Long) @Insert(onConflict = OnConflictStrategy.REPLACE) @@ -46,7 +46,7 @@ abstract class ConversationsDao { abstract suspend fun saveConversationsWithInsert(vararg conversations: ConversationEntity): List @Query( - "UPDATE conversations SET changing = :changing WHERE user = :userId AND conversation_id = :conversationId" + "UPDATE conversations SET changing = :changing WHERE user_id = :userId AND conversation_id = :conversationId" ) abstract suspend fun updateChangingValueForConversation( userId: Long, @@ -55,7 +55,7 @@ abstract class ConversationsDao { ) @Query( - "UPDATE conversations SET favorite = :favorite, changing = 0 WHERE user = :userId AND conversation_id = :conversationId" + "UPDATE conversations SET favorite = :favorite, changing = 0 WHERE user_id = :userId AND conversation_id = :conversationId" ) abstract suspend fun updateFavoriteValueForConversation( userId: Long, @@ -63,7 +63,7 @@ abstract class ConversationsDao { favorite: Boolean ) - @Query("DELETE FROM conversations WHERE user = :userId AND conversation_id = :conversationId") + @Query("DELETE FROM conversations WHERE user_id = :userId AND conversation_id = :conversationId") abstract suspend fun deleteConversation( userId: Long, conversationId: String @@ -72,12 +72,15 @@ abstract class ConversationsDao { @Delete abstract suspend fun deleteConversations(vararg conversation: ConversationEntity) - @Query("DELETE FROM conversations WHERE user = :userId AND modified_at < :timestamp") + @Query("DELETE FROM conversations WHERE user_id = :userId AND modified_at < :timestamp") abstract suspend fun deleteConversationsForUserWithTimestamp( userId: Long, timestamp: Long ) + @Query("SELECT * FROM conversations where id = :userId AND token = :token") + abstract suspend fun getConversationForUserWithToken(userId: Long, token: String): ConversationEntity? + @Transaction open suspend fun updateConversationsForUser( userId: Long, diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt index 1f7636e3f..1dc3cde90 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt @@ -37,11 +37,11 @@ import java.util.HashMap @Entity( tableName = "conversations", - indices = [Index(value = ["user", "conversation_id"], unique = true)], + indices = [Index(value = ["user_id", "token"], unique = true)], foreignKeys = [ForeignKey( entity = UserNgEntity::class, parentColumns = arrayOf("id"), - childColumns = arrayOf("user"), + childColumns = arrayOf("user_id"), onDelete = CASCADE, onUpdate = CASCADE, deferred = true @@ -49,7 +49,7 @@ import java.util.HashMap ) data class ConversationEntity( @PrimaryKey @ColumnInfo(name = "id") var id: String, - @ColumnInfo(name = "user") var user: Long? = null, + @ColumnInfo(name = "user_id") var user: Long? = null, @ColumnInfo(name = "conversation_id") var conversationId: String? = null, @ColumnInfo(name = "token") var token: String? = null, @ColumnInfo(name = "name") var name: String? = null, @@ -117,7 +117,7 @@ fun ConversationEntity.toConversation(): Conversation { } fun Conversation.toConversationEntity(): ConversationEntity { - val conversationEntity = ConversationEntity(this.internalUserId.toString() + "@" + this.conversationId) + val conversationEntity = ConversationEntity(this.internalUserId.toString() + "@" + this.token) conversationEntity.user = this.internalUserId conversationEntity.conversationId = this.conversationId conversationEntity.token = this.token diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt index 76ac62e17..a76d96e8c 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt @@ -39,7 +39,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType foreignKeys = [ForeignKey( entity = ConversationEntity::class, parentColumns = arrayOf("id"), - childColumns = arrayOf("conversation"), + childColumns = arrayOf("conversation_id"), onDelete = CASCADE, onUpdate = CASCADE, deferred = true @@ -47,7 +47,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType ) data class MessageEntity( @PrimaryKey @ColumnInfo(name = "id") var id: String, - @ColumnInfo(name = "conversation") var conversation: String, + @ColumnInfo(name = "conversation_id") var conversation: String, @ColumnInfo(name = "message_id") var messageId: Long = 0, @ColumnInfo(name = "actor_id") var actorId: String? = null, @ColumnInfo(name = "actor_type") var actorType: String? = null, diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/UserNgEntity.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/UserNgEntity.kt index 08aa554eb..69ab60c63 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/models/UserNgEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/UserNgEntity.kt @@ -86,9 +86,5 @@ fun UserNgEntity.hasSpreedFeatureCapability(capabilityName: String): Boolean { fun UserNgEntity.maxMessageLength(): Int { val maxLength = capabilities?.spreedCapability?.config?.get("chat") ?.get("max-length") - if (maxLength != null) { - return maxLength.toInt() - } else { - return 1000 - } + return maxLength?.toInt() ?: 1000 } diff --git a/app/src/main/java/com/nextcloud/talk/presenters/MentionAutocompletePresenter.kt b/app/src/main/java/com/nextcloud/talk/presenters/MentionAutocompletePresenter.kt index 5e785e254..57ec2e11f 100644 --- a/app/src/main/java/com/nextcloud/talk/presenters/MentionAutocompletePresenter.kt +++ b/app/src/main/java/com/nextcloud/talk/presenters/MentionAutocompletePresenter.kt @@ -22,7 +22,6 @@ package com.nextcloud.talk.presenters import android.content.Context import android.view.View import androidx.recyclerview.widget.RecyclerView -import autodagger.AutoInjector import com.nextcloud.talk.adapters.items.MentionAutocompleteItem import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication @@ -42,13 +41,9 @@ import io.reactivex.schedulers.Schedulers import org.koin.core.KoinComponent import org.koin.core.inject import java.util.* -import javax.inject.Inject -@AutoInjector(NextcloudTalkApplication::class) class MentionAutocompletePresenter : RecyclerViewPresenter, FlexibleAdapter.OnItemClickListener, KoinComponent { - @JvmField @Inject - var ncApi: NcApi? = null - + val ncApi: NcApi by inject() val usersRepository: UsersRepository by inject() private var currentUser: UserNgEntity? private var adapter: FlexibleAdapter>? = null @@ -58,18 +53,12 @@ class MentionAutocompletePresenter : RecyclerViewPresenter, FlexibleAd constructor(context: Context) : super(context) { this.internalContext = context - sharedApplication - ?.componentApplication - ?.inject(this) currentUser = usersRepository.getActiveUser() } constructor(context: Context, roomToken: String?) : super(context) { this.roomToken = roomToken this.internalContext = context - sharedApplication - ?.componentApplication - ?.inject(this) currentUser = usersRepository.getActiveUser() } diff --git a/app/src/main/java/com/nextcloud/talk/receivers/PackageReplacedReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/PackageReplacedReceiver.kt index d32dffd72..62faa006b 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/PackageReplacedReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/PackageReplacedReceiver.kt @@ -27,27 +27,21 @@ import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.util.Log -import autodagger.AutoInjector -import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.preferences.AppPreferences -import javax.inject.Inject +import org.koin.core.KoinComponent +import org.koin.core.inject -@AutoInjector(NextcloudTalkApplication::class) -class PackageReplacedReceiver : BroadcastReceiver() { +class PackageReplacedReceiver : BroadcastReceiver(), KoinComponent { - @Inject - lateinit var userUtils: UserUtils - - @Inject - lateinit var appPreferences: AppPreferences + val userUtils: UserUtils by inject() + val appPreferences: AppPreferences by inject() override fun onReceive( context: Context, intent: Intent? ) { - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) if (intent != null && intent.action != null && intent.action == "android.intent.action.MY_PACKAGE_REPLACED" diff --git a/app/src/main/java/com/nextcloud/talk/utils/AccountUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/AccountUtils.kt index 84c4c9a37..fc9ae400c 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/AccountUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/AccountUtils.kt @@ -32,6 +32,7 @@ import com.nextcloud.talk.R import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.models.ImportAccount import com.nextcloud.talk.models.database.UserEntity +import com.nextcloud.talk.newarch.local.models.UserNgEntity import java.util.ArrayList import java.util.Arrays @@ -39,14 +40,14 @@ object AccountUtils { private val TAG = "AccountUtils" - fun findAccounts(userEntitiesList: List): List { + fun findAccounts(userEntitiesList: List): List { val context = NextcloudTalkApplication.sharedApplication!!.applicationContext val accMgr = AccountManager.get(context) val accounts = accMgr.getAccountsByType(context.getString(R.string.nc_import_account_type)) val accountsAvailable = ArrayList() var importAccount: ImportAccount - var internalUserEntity: UserEntity + var internalUserEntity: UserNgEntity var accountFound: Boolean for (account in accounts) { accountFound = false diff --git a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt index 7927c1f4d..3487432bc 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -29,7 +29,6 @@ import android.content.Context import android.os.Build import android.service.notification.StatusBarNotification import com.nextcloud.talk.R -import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.utils.bundle.BundleKeys diff --git a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt index 0255c3e41..c4207da2b 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt @@ -24,11 +24,9 @@ import android.content.Context import android.text.TextUtils import android.util.Base64 import android.util.Log -import autodagger.AutoInjector import com.bluelinelabs.logansquare.LoganSquare import com.nextcloud.talk.R.string import com.nextcloud.talk.api.NcApi -import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.events.EventStatus import com.nextcloud.talk.events.EventStatus.EventType.PUSH_REGISTRATION @@ -44,42 +42,21 @@ import io.reactivex.Observer import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import org.greenrobot.eventbus.EventBus -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.security.InvalidKeyException -import java.security.Key -import java.security.KeyFactory -import java.security.KeyPair -import java.security.KeyPairGenerator -import java.security.MessageDigest -import java.security.NoSuchAlgorithmException -import java.security.PublicKey -import java.security.Signature -import java.security.SignatureException +import org.koin.core.KoinComponent +import org.koin.core.inject +import java.io.* +import java.security.* import java.security.spec.InvalidKeySpecException import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec -import java.util.HashMap -import javax.inject.Inject +import java.util.* import kotlin.experimental.and -@AutoInjector(NextcloudTalkApplication::class) -class PushUtils(val usersRepository: UsersRepository) { - @JvmField - @Inject - var userUtils: UserUtils? = null - @JvmField - @Inject - var appPreferences: AppPreferences? = null - @JvmField - @Inject - var eventBus: EventBus? = null - @JvmField - @Inject - var ncApi: NcApi? = null +class PushUtils(val usersRepository: UsersRepository): KoinComponent { + val userUtils: UserUtils by inject() + val appPreferences: AppPreferences by inject() + val eventBus: EventBus by inject() + val ncApi: NcApi by inject() private val keysFile: File private val publicKeyFile: File private val privateKeyFile: File @@ -426,9 +403,6 @@ class PushUtils(val usersRepository: UsersRepository) { } init { - sharedApplication!! - .componentApplication - .inject(this) keysFile = sharedApplication!! .getDir("PushKeyStore", Context.MODE_PRIVATE) publicKeyFile = File( diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/arbitrarystorage/ArbitraryStorageModule.java b/app/src/main/java/com/nextcloud/talk/utils/database/arbitrarystorage/ArbitraryStorageModule.java deleted file mode 100644 index 3b0d4e204..000000000 --- a/app/src/main/java/com/nextcloud/talk/utils/database/arbitrarystorage/ArbitraryStorageModule.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.utils.database.arbitrarystorage; - -import autodagger.AutoInjector; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.dagger.modules.DatabaseModule; -import dagger.Module; -import dagger.Provides; -import io.requery.Persistable; -import io.requery.reactivex.ReactiveEntityStore; -import javax.inject.Inject; - -@Module(includes = DatabaseModule.class) -@AutoInjector(NextcloudTalkApplication.class) -public class ArbitraryStorageModule { - - @Inject - public ArbitraryStorageModule() { - } - - @Provides - public ArbitraryStorageUtils provideArbitraryStorageUtils( - ReactiveEntityStore dataStore) { - return new ArbitraryStorageUtils(dataStore); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/arbitrarystorage/ArbitraryStorageUtils.java b/app/src/main/java/com/nextcloud/talk/utils/database/arbitrarystorage/ArbitraryStorageUtils.java index cbc5e59a2..1c7b46eab 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/database/arbitrarystorage/ArbitraryStorageUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/database/arbitrarystorage/ArbitraryStorageUtils.java @@ -32,7 +32,7 @@ import io.requery.reactivex.ReactiveScalar; public class ArbitraryStorageUtils { private ReactiveEntityStore dataStore; - ArbitraryStorageUtils(ReactiveEntityStore dataStore) { + public ArbitraryStorageUtils(ReactiveEntityStore dataStore) { this.dataStore = dataStore; } diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/UserModule.java b/app/src/main/java/com/nextcloud/talk/utils/database/user/UserModule.java deleted file mode 100644 index a30862268..000000000 --- a/app/src/main/java/com/nextcloud/talk/utils/database/user/UserModule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.utils.database.user; - -import autodagger.AutoInjector; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.dagger.modules.DatabaseModule; -import dagger.Module; -import dagger.Provides; -import io.requery.Persistable; -import io.requery.reactivex.ReactiveEntityStore; -import javax.inject.Inject; - -@Module(includes = DatabaseModule.class) -@AutoInjector(NextcloudTalkApplication.class) -public class UserModule { - - @Inject - public UserModule() { - } - - @Provides - public UserUtils provideUserUtils(ReactiveEntityStore dataStore) { - return new UserUtils(dataStore); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.java b/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.java deleted file mode 100644 index 495438bae..000000000 --- a/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This class is in part based on the code from the great people that wrote Signal - * https://github.com/signalapp/Signal-Android/raw/f9adb4e4554a44fd65b77320e34bf4bccf7924ce/src/org/thoughtcrime/securesms/webrtc/locks/LockManager.java - */ - -package com.nextcloud.talk.utils.power; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Configuration; -import android.net.wifi.WifiManager; -import android.os.Build; -import android.os.PowerManager; -import android.provider.Settings; -import autodagger.AutoInjector; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import javax.inject.Inject; - -@AutoInjector(NextcloudTalkApplication.class) - -public class PowerManagerUtils { - private static final String TAG = "PowerManagerUtils"; - private final PowerManager.WakeLock fullLock; - private final PowerManager.WakeLock partialLock; - private final WifiManager.WifiLock wifiLock; - private final boolean wifiLockEnforced; - @Inject - Context context; - private ProximityLock proximityLock; - private boolean proximityDisabled = false; - - private int orientation; - - public PowerManagerUtils() { - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - fullLock = - pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, - "nctalk:fullwakelock"); - partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nctalk:partialwakelock"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - proximityLock = new ProximityLock(pm); - } - - // we suppress a possible leak because this is indeed application context - @SuppressLint("WifiManagerPotentialLeak") WifiManager wm = - (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "nctalk:wifiwakelock"); - - fullLock.setReferenceCounted(false); - partialLock.setReferenceCounted(false); - wifiLock.setReferenceCounted(false); - - wifiLockEnforced = isWifiPowerActiveModeEnabled(context); - orientation = context.getResources().getConfiguration().orientation; - } - - public void setOrientation(int newOrientation) { - orientation = newOrientation; - updateInCallWakeLockState(); - } - - public void updatePhoneState(PhoneState state) { - switch (state) { - case IDLE: - setWakeLockState(WakeLockState.SLEEP); - break; - case PROCESSING: - setWakeLockState(WakeLockState.PARTIAL); - break; - case INTERACTIVE: - setWakeLockState(WakeLockState.FULL); - break; - case WITH_PROXIMITY_SENSOR_LOCK: - proximityDisabled = false; - updateInCallWakeLockState(); - break; - case WITHOUT_PROXIMITY_SENSOR_LOCK: - proximityDisabled = true; - updateInCallWakeLockState(); - break; - } - } - - private void updateInCallWakeLockState() { - if (orientation != Configuration.ORIENTATION_LANDSCAPE - && wifiLockEnforced - && !proximityDisabled) { - setWakeLockState(WakeLockState.PROXIMITY); - } else { - setWakeLockState(WakeLockState.FULL); - } - } - - private boolean isWifiPowerActiveModeEnabled(Context context) { - int wifi_pwr_active_mode = - Settings.Secure.getInt(context.getContentResolver(), "wifi_pwr_active_mode", -1); - return (wifi_pwr_active_mode != 0); - } - - @SuppressLint("WakelockTimeout") - private synchronized void setWakeLockState(WakeLockState newState) { - switch (newState) { - case FULL: - if (!fullLock.isHeld()) { - fullLock.acquire(); - } - - if (!partialLock.isHeld()) { - partialLock.acquire(); - } - - if (!wifiLock.isHeld()) { - wifiLock.acquire(); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - proximityLock.release(); - } - break; - case PARTIAL: - if (!partialLock.isHeld()) { - partialLock.acquire(); - } - - if (!wifiLock.isHeld()) { - wifiLock.acquire(); - } - - fullLock.release(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - proximityLock.release(); - } - break; - case SLEEP: - fullLock.release(); - partialLock.release(); - wifiLock.release(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - proximityLock.release(); - } - break; - case PROXIMITY: - if (!partialLock.isHeld()) { - partialLock.acquire(); - } - - if (!wifiLock.isHeld()) { - wifiLock.acquire(); - } - - fullLock.release( - - ); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - proximityLock.acquire(); - } - break; - default: - // something went very very wrong - } - } - - public enum PhoneState { - IDLE, - PROCESSING, //used when the phone is active but before the user should be alerted. - INTERACTIVE, - WITHOUT_PROXIMITY_SENSOR_LOCK, - WITH_PROXIMITY_SENSOR_LOCK - } - - public enum WakeLockState { - FULL, - PARTIAL, - SLEEP, - PROXIMITY - } -} diff --git a/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt new file mode 100644 index 000000000..3ec815ec7 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt @@ -0,0 +1,166 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This class is in part based on the code from the great people that wrote Signal + * https://github.com/signalapp/Signal-Android/raw/f9adb4e4554a44fd65b77320e34bf4bccf7924ce/src/org/thoughtcrime/securesms/webrtc/locks/LockManager.java + */ +package com.nextcloud.talk.utils.power + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.Configuration +import android.net.wifi.WifiManager +import android.net.wifi.WifiManager.WifiLock +import android.os.Build +import android.os.PowerManager +import android.os.PowerManager.WakeLock +import android.provider.Settings +import org.koin.core.KoinComponent +import org.koin.core.inject + +class PowerManagerUtils: KoinComponent { + private val fullLock: WakeLock + private val partialLock: WakeLock + private val wifiLock: WifiLock + private val wifiLockEnforced: Boolean + val context: Context by inject() + private var proximityLock: ProximityLock? = null + private var proximityDisabled = false + private var orientation: Int + fun setOrientation(newOrientation: Int) { + orientation = newOrientation + updateInCallWakeLockState() + } + + fun updatePhoneState(state: PhoneState?) { + when (state) { + PhoneState.IDLE -> setWakeLockState(WakeLockState.SLEEP) + PhoneState.PROCESSING -> setWakeLockState(WakeLockState.PARTIAL) + PhoneState.INTERACTIVE -> setWakeLockState(WakeLockState.FULL) + PhoneState.WITH_PROXIMITY_SENSOR_LOCK -> { + proximityDisabled = false + updateInCallWakeLockState() + } + PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK -> { + proximityDisabled = true + updateInCallWakeLockState() + } + } + } + + private fun updateInCallWakeLockState() { + if (orientation != Configuration.ORIENTATION_LANDSCAPE && wifiLockEnforced + && !proximityDisabled) { + setWakeLockState(WakeLockState.PROXIMITY) + } else { + setWakeLockState(WakeLockState.FULL) + } + } + + private fun isWifiPowerActiveModeEnabled(context: Context?): Boolean { + val wifi_pwr_active_mode = Settings.Secure.getInt(context!!.contentResolver, "wifi_pwr_active_mode", -1) + return wifi_pwr_active_mode != 0 + } + + @SuppressLint("WakelockTimeout") + @Synchronized + private fun setWakeLockState(newState: WakeLockState) { + when (newState) { + WakeLockState.FULL -> { + if (!fullLock.isHeld) { + fullLock.acquire() + } + if (!partialLock.isHeld) { + partialLock.acquire() + } + if (!wifiLock.isHeld) { + wifiLock.acquire() + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + proximityLock!!.release() + } + } + WakeLockState.PARTIAL -> { + if (!partialLock.isHeld) { + partialLock.acquire() + } + if (!wifiLock.isHeld) { + wifiLock.acquire() + } + fullLock.release() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + proximityLock!!.release() + } + } + WakeLockState.SLEEP -> { + fullLock.release() + partialLock.release() + wifiLock.release() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + proximityLock!!.release() + } + } + WakeLockState.PROXIMITY -> { + if (!partialLock.isHeld) { + partialLock.acquire() + } + if (!wifiLock.isHeld) { + wifiLock.acquire() + } + fullLock.release() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + proximityLock!!.acquire() + } + } + else -> { + } + } + } + + enum class PhoneState { + IDLE, PROCESSING, //used when the phone is active but before the user should be alerted. + INTERACTIVE, WITHOUT_PROXIMITY_SENSOR_LOCK, WITH_PROXIMITY_SENSOR_LOCK + } + + enum class WakeLockState { + FULL, PARTIAL, SLEEP, PROXIMITY + } + + companion object { + private const val TAG = "PowerManagerUtils" + } + + init { + val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager + fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, + "nctalk:fullwakelock") + partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nctalk:partialwakelock") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + proximityLock = ProximityLock(pm) + } + // we suppress a possible leak because this is indeed application context + @SuppressLint("WifiManagerPotentialLeak") val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager + wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "nctalk:wifiwakelock") + fullLock.setReferenceCounted(false) + partialLock.setReferenceCounted(false) + wifiLock.setReferenceCounted(false) + wifiLockEnforced = isWifiPowerActiveModeEnabled(context) + orientation = context.resources.configuration.orientation + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/MagicUserInputModule.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/MagicUserInputModule.java deleted file mode 100644 index 4fd4891c3..000000000 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/MagicUserInputModule.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.utils.preferences; - -import android.app.Dialog; -import android.content.Context; -import android.os.Build; -import android.text.InputType; -import android.view.LayoutInflater; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import androidx.appcompat.app.AlertDialog; -import autodagger.AutoInjector; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.yarolegovich.mp.io.StandardUserInputModule; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; - -@AutoInjector(NextcloudTalkApplication.class) -public class MagicUserInputModule extends StandardUserInputModule { - - @Inject - AppPreferences appPreferences; - - private List keysWithIntegerInput = new ArrayList<>(); - - public MagicUserInputModule(Context context) { - super(context); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - } - - public MagicUserInputModule(Context context, List keysWithIntegerInput) { - super(context); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - this.keysWithIntegerInput = keysWithIntegerInput; - } - - @Override - public void showEditTextInput( - String key, - CharSequence title, - CharSequence defaultValue, - final Listener listener) { - final View view = LayoutInflater.from(context).inflate(R.layout.dialog_edittext, null); - final EditText inputField = view.findViewById(R.id.mp_text_input); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.getIsKeyboardIncognito()) { - inputField.setImeOptions( - inputField.getImeOptions() | EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING); - } - - if (defaultValue != null) { - inputField.setText(defaultValue); - inputField.setSelection(defaultValue.length()); - } - - if (keysWithIntegerInput.contains(key)) { - inputField.setInputType(InputType.TYPE_CLASS_NUMBER); - } - - final Dialog dialog = new AlertDialog.Builder(context) - .setTitle(title) - .setView(view) - .show(); - view.findViewById(R.id.mp_btn_confirm).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - listener.onInput(inputField.getText().toString()); - dialog.dismiss(); - } - }); - } -} diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/MagicUserInputModule.kt b/app/src/main/java/com/nextcloud/talk/utils/preferences/MagicUserInputModule.kt new file mode 100644 index 000000000..9d8202c56 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/MagicUserInputModule.kt @@ -0,0 +1,73 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.utils.preferences + +import android.app.Dialog +import android.content.Context +import android.os.Build +import android.text.InputType +import android.view.LayoutInflater +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import androidx.appcompat.app.AlertDialog +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.yarolegovich.mp.io.StandardUserInputModule +import com.yarolegovich.mp.io.UserInputModule +import org.koin.core.KoinComponent +import org.koin.core.inject +import java.util.* + +class MagicUserInputModule : StandardUserInputModule, KoinComponent { + val appPreferences: AppPreferences by inject() + private var keysWithIntegerInput: List = ArrayList() + + constructor(context: Context?, keysWithIntegerInput: List) : super(context) { + this.keysWithIntegerInput = keysWithIntegerInput + } + + override fun showEditTextInput( + key: String, + title: CharSequence, + defaultValue: CharSequence, + listener: UserInputModule.Listener) { + val view = LayoutInflater.from(context).inflate(R.layout.dialog_edittext, null) + val inputField = view.findViewById(R.id.mp_text_input) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences!!.isKeyboardIncognito) { + inputField.imeOptions = inputField.imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING + } + if (defaultValue != null) { + inputField.setText(defaultValue) + inputField.setSelection(defaultValue.length) + } + if (keysWithIntegerInput.contains(key)) { + inputField.inputType = InputType.TYPE_CLASS_NUMBER + } + val dialog: Dialog = AlertDialog.Builder(context) + .setTitle(title) + .setView(view) + .show() + view.findViewById(R.id.mp_btn_confirm).setOnClickListener { + listener.onInput(inputField.text.toString()) + dialog.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.kt b/app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.kt index e2070cc85..7c551b3d5 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/preferencestorage/DatabaseStorageModule.kt @@ -22,7 +22,6 @@ package com.nextcloud.talk.utils.preferences.preferencestorage import android.os.Bundle import android.text.TextUtils -import autodagger.AutoInjector import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication @@ -37,21 +36,16 @@ import com.yarolegovich.mp.io.StorageModule import io.reactivex.Observer import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers -import javax.inject.Inject +import org.koin.core.KoinComponent +import org.koin.core.inject -@AutoInjector(NextcloudTalkApplication::class) class DatabaseStorageModule( private val conversationUser: UserNgEntity, private val conversationToken: String, private val conversationInfoInterface: ConversationInfoInterface -) : StorageModule { - @JvmField - @Inject - var arbitraryStorageUtils: ArbitraryStorageUtils? = - null - @JvmField - @Inject - var ncApi: NcApi? = null +) : StorageModule, KoinComponent { + val arbitraryStorageUtils: ArbitraryStorageUtils by inject() + val ncApi: NcApi by inject() private val accountIdentifier: Long private var lobbyValue = false private var favoriteConversationValue = false @@ -272,9 +266,6 @@ class DatabaseStorageModule( override fun onRestoreInstanceState(savedState: Bundle) {} init { - sharedApplication!! - .componentApplication - .inject(this) accountIdentifier = conversationUser.id!! } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/utils/ssl/MagicKeyManager.java b/app/src/main/java/com/nextcloud/talk/utils/ssl/MagicKeyManager.java deleted file mode 100644 index 4ae805faf..000000000 --- a/app/src/main/java/com/nextcloud/talk/utils/ssl/MagicKeyManager.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.utils.ssl; - -import android.content.Context; -import android.security.KeyChain; -import android.security.KeyChainException; -import android.text.TextUtils; -import android.util.Log; -import androidx.annotation.Nullable; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.utils.database.user.UserUtils; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import java.net.Socket; -import java.security.Principal; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.net.ssl.X509KeyManager; - -public class MagicKeyManager implements X509KeyManager { - private static final String TAG = "MagicKeyManager"; - private final X509KeyManager keyManager; - - private UserUtils userUtils; - private AppPreferences appPreferences; - private Context context; - - public MagicKeyManager(X509KeyManager keyManager, UserUtils userUtils, - AppPreferences appPreferences) { - this.keyManager = keyManager; - this.userUtils = userUtils; - this.appPreferences = appPreferences; - - context = NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext(); - } - - @Override - public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { - String alias; - if (!TextUtils.isEmpty(alias = userUtils.getCurrentUser().getClientCertificate()) || - !TextUtils.isEmpty(alias = appPreferences.getTemporaryClientCertAlias()) - && new ArrayList<>(Arrays.asList(getClientAliases())).contains(alias)) { - return alias; - } - - return null; - } - - @Override - public String chooseServerAlias(String s, Principal[] principals, Socket socket) { - return null; - } - - private X509Certificate[] getCertificatesForAlias(@Nullable String alias) { - if (alias != null) { - GetCertificatesForAliasRunnable getCertificatesForAliasRunnable = - new GetCertificatesForAliasRunnable(alias); - Thread getCertificatesThread = new Thread(getCertificatesForAliasRunnable); - getCertificatesThread.start(); - try { - getCertificatesThread.join(); - return getCertificatesForAliasRunnable.getCertificates(); - } catch (InterruptedException e) { - Log.e(TAG, - "Failed to join the thread while getting certificates: " + e.getLocalizedMessage()); - } - } - - return null; - } - - private PrivateKey getPrivateKeyForAlias(@Nullable String alias) { - if (alias != null) { - GetPrivateKeyForAliasRunnable getPrivateKeyForAliasRunnable = - new GetPrivateKeyForAliasRunnable(alias); - Thread getPrivateKeyThread = new Thread(getPrivateKeyForAliasRunnable); - getPrivateKeyThread.start(); - try { - getPrivateKeyThread.join(); - return getPrivateKeyForAliasRunnable.getPrivateKey(); - } catch (InterruptedException e) { - Log.e(TAG, - "Failed to join the thread while getting private key: " + e.getLocalizedMessage()); - } - } - - return null; - } - - @Override - public X509Certificate[] getCertificateChain(String s) { - if (new ArrayList<>(Arrays.asList(getClientAliases())).contains(s)) { - return getCertificatesForAlias(s); - } - - return null; - } - - private String[] getClientAliases() { - Set aliases = new HashSet<>(); - String alias; - if (!TextUtils.isEmpty(alias = appPreferences.getTemporaryClientCertAlias())) { - aliases.add(alias); - } - - List userEntities = userUtils.getUsers(); - for (int i = 0; i < userEntities.size(); i++) { - if (!TextUtils.isEmpty(alias = userEntities.get(i).getClientCertificate())) { - aliases.add(alias); - } - } - - return aliases.toArray(new String[aliases.size()]); - } - - @Override - public String[] getClientAliases(String s, Principal[] principals) { - return getClientAliases(); - } - - @Override - public String[] getServerAliases(String s, Principal[] principals) { - return null; - } - - @Override - public PrivateKey getPrivateKey(String s) { - if (new ArrayList<>(Arrays.asList(getClientAliases())).contains(s)) { - return getPrivateKeyForAlias(s); - } - - return null; - } - - private class GetCertificatesForAliasRunnable implements Runnable { - private volatile X509Certificate[] certificates; - private String alias; - - public GetCertificatesForAliasRunnable(String alias) { - this.alias = alias; - } - - @Override - public void run() { - try { - certificates = KeyChain.getCertificateChain(context, alias); - } catch (KeyChainException | InterruptedException e) { - Log.e(TAG, e.getLocalizedMessage()); - } - } - - public X509Certificate[] getCertificates() { - return certificates; - } - } - - private class GetPrivateKeyForAliasRunnable implements Runnable { - private volatile PrivateKey privateKey; - private String alias; - - public GetPrivateKeyForAliasRunnable(String alias) { - this.alias = alias; - } - - @Override - public void run() { - try { - privateKey = KeyChain.getPrivateKey(context, alias); - } catch (KeyChainException | InterruptedException e) { - Log.e(TAG, e.getLocalizedMessage()); - } - } - - public PrivateKey getPrivateKey() { - return privateKey; - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/utils/ssl/MagicKeyManager.kt b/app/src/main/java/com/nextcloud/talk/utils/ssl/MagicKeyManager.kt new file mode 100644 index 000000000..a31ea583b --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/ssl/MagicKeyManager.kt @@ -0,0 +1,167 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.utils.ssl + +import android.content.Context +import android.security.KeyChain +import android.security.KeyChainException +import android.text.TextUtils +import android.util.Log +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.models.database.UserEntity +import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.utils.preferences.AppPreferences +import java.net.Socket +import java.security.Principal +import java.security.PrivateKey +import java.security.cert.X509Certificate +import java.util.* +import javax.net.ssl.X509KeyManager + +class MagicKeyManager(private val keyManager: X509KeyManager, private val usersRepository: UsersRepository, + private val appPreferences: AppPreferences) : X509KeyManager { + private val context: Context = sharedApplication!!.applicationContext + override fun chooseClientAlias(strings: Array, principals: Array, socket: Socket): String? { + var alias: String? = null + val user = usersRepository.getActiveUser() + user?.let { + it.clientCertificate?.let { + alias = it + }?: run { + appPreferences.temporaryClientCertAlias?.let { + alias = it + } + } + } + + return alias + } + + override fun chooseServerAlias(s: String, principals: Array, socket: Socket): String? { + return null + } + + private fun getCertificatesForAlias(alias: String?): Array? { + if (alias != null) { + val getCertificatesForAliasRunnable = GetCertificatesForAliasRunnable(alias) + val getCertificatesThread = Thread(getCertificatesForAliasRunnable) + getCertificatesThread.start() + try { + getCertificatesThread.join() + return getCertificatesForAliasRunnable.certificates + } catch (e: InterruptedException) { + Log.e(TAG, + "Failed to join the thread while getting certificates: " + e.localizedMessage) + } + } + return null + } + + private fun getPrivateKeyForAlias(alias: String?): PrivateKey? { + if (alias != null) { + val getPrivateKeyForAliasRunnable = GetPrivateKeyForAliasRunnable(alias) + val getPrivateKeyThread = Thread(getPrivateKeyForAliasRunnable) + getPrivateKeyThread.start() + try { + getPrivateKeyThread.join() + return getPrivateKeyForAliasRunnable.privateKey + } catch (e: InterruptedException) { + Log.e(TAG, + "Failed to join the thread while getting private key: " + e.localizedMessage) + } + } + return null + } + + override fun getCertificateChain(s: String): Array? { + return if (ArrayList(listOf(*clientAliases)).contains(s)) { + getCertificatesForAlias(s)!! + } else null + } + + private val clientAliases: Array + private get() { + val aliases: MutableSet = HashSet() + var alias: String + if (!TextUtils.isEmpty(appPreferences.temporaryClientCertAlias.also { alias = it })) { + aliases.add(alias) + } + val userEntities: List = usersRepository.getUsers() + for (i in userEntities.indices) { + userEntities[i].clientCertificate?.let { + aliases.add(it) + } + } + return aliases.toTypedArray() + } + + override fun getClientAliases(s: String, principals: Array): Array { + return clientAliases + } + + override fun getServerAliases(s: String, principals: Array): Array? { + return null + } + + override fun getPrivateKey(s: String): PrivateKey? { + return if (ArrayList(listOf(*clientAliases)).contains(s)) { + getPrivateKeyForAlias(s)!! + } else null + } + + private inner class GetCertificatesForAliasRunnable(private val alias: String) : Runnable { + @Volatile + lateinit var certificates: Array + + override fun run() { + try { + certificates = KeyChain.getCertificateChain(context, alias) as Array + } catch (e: KeyChainException) { + Log.e(TAG, e.localizedMessage) + } catch (e: InterruptedException) { + Log.e(TAG, e.localizedMessage) + } + } + + } + + private inner class GetPrivateKeyForAliasRunnable(private val alias: String) : Runnable { + @Volatile + var privateKey: PrivateKey? = null + private set + + override fun run() { + try { + privateKey = KeyChain.getPrivateKey(context, alias) + } catch (e: KeyChainException) { + Log.e(TAG, e.localizedMessage) + } catch (e: InterruptedException) { + Log.e(TAG, e.localizedMessage) + } + } + + } + + companion object { + private const val TAG = "MagicKeyManager" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java b/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java deleted file mode 100644 index 9cdb4da1c..000000000 --- a/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package com.nextcloud.talk.webrtc; - -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; -import androidx.annotation.Nullable; -import autodagger.AutoInjector; -import com.bluelinelabs.logansquare.LoganSquare; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.events.MediaStreamEvent; -import com.nextcloud.talk.events.PeerConnectionEvent; -import com.nextcloud.talk.events.SessionDescriptionSendEvent; -import com.nextcloud.talk.events.WebSocketCommunicationEvent; -import com.nextcloud.talk.models.json.signaling.DataChannelMessage; -import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick; -import com.nextcloud.talk.models.json.signaling.NCIceCandidate; -import com.nextcloud.talk.utils.LoggingUtils; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import javax.inject.Inject; -import org.greenrobot.eventbus.EventBus; -import org.webrtc.DataChannel; -import org.webrtc.IceCandidate; -import org.webrtc.MediaConstraints; -import org.webrtc.MediaStream; -import org.webrtc.PeerConnection; -import org.webrtc.PeerConnectionFactory; -import org.webrtc.RtpReceiver; -import org.webrtc.SdpObserver; -import org.webrtc.SessionDescription; - -@AutoInjector(NextcloudTalkApplication.class) -public class MagicPeerConnectionWrapper { - private static String TAG = "MagicPeerConnectionWrapper"; - @Inject - Context context; - private List iceCandidates = new ArrayList<>(); - private PeerConnection peerConnection; - private String sessionId; - private String nick; - private MediaConstraints sdpConstraints; - private DataChannel magicDataChannel; - private MagicSdpObserver magicSdpObserver; - private MediaStream remoteMediaStream; - private boolean remoteVideoOn; - private boolean remoteAudioOn; - private boolean hasInitiated; - private MediaStream localMediaStream; - private boolean isMCUPublisher; - private boolean hasMCU; - private String videoStreamType; - private int connectionAttempts = 0; - private PeerConnection.IceConnectionState peerIceConnectionState; - - public MagicPeerConnectionWrapper(PeerConnectionFactory peerConnectionFactory, - List iceServerList, - MediaConstraints sdpConstraints, - String sessionId, String localSession, @Nullable MediaStream mediaStream, - boolean isMCUPublisher, boolean hasMCU, String videoStreamType) { - - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - - this.localMediaStream = mediaStream; - this.videoStreamType = videoStreamType; - this.hasMCU = hasMCU; - - this.sessionId = sessionId; - this.sdpConstraints = sdpConstraints; - - magicSdpObserver = new MagicSdpObserver(); - hasInitiated = sessionId.compareTo(localSession) < 0; - this.isMCUPublisher = isMCUPublisher; - - peerConnection = peerConnectionFactory.createPeerConnection(iceServerList, sdpConstraints, - new MagicPeerConnectionObserver()); - - if (peerConnection != null) { - if (localMediaStream != null) { - peerConnection.addStream(localMediaStream); - } - - if (hasMCU || hasInitiated) { - DataChannel.Init init = new DataChannel.Init(); - init.negotiated = false; - magicDataChannel = peerConnection.createDataChannel("status", init); - magicDataChannel.registerObserver(new MagicDataChannelObserver()); - if (isMCUPublisher) { - peerConnection.createOffer(magicSdpObserver, sdpConstraints); - } else if (hasMCU) { - HashMap hashMap = new HashMap<>(); - hashMap.put("sessionId", sessionId); - EventBus.getDefault() - .post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap)); - } else if (hasInitiated) { - peerConnection.createOffer(magicSdpObserver, sdpConstraints); - } - } - } - } - - public String getVideoStreamType() { - return videoStreamType; - } - - public void removePeerConnection() { - if (magicDataChannel != null) { - magicDataChannel.dispose(); - magicDataChannel = null; - } - - if (peerConnection != null) { - if (localMediaStream != null) { - peerConnection.removeStream(localMediaStream); - } - - peerConnection.close(); - peerConnection = null; - } - } - - public void drainIceCandidates() { - - if (peerConnection != null) { - for (IceCandidate iceCandidate : iceCandidates) { - peerConnection.addIceCandidate(iceCandidate); - } - - iceCandidates = new ArrayList<>(); - } - } - - public MagicSdpObserver getMagicSdpObserver() { - return magicSdpObserver; - } - - public void addCandidate(IceCandidate iceCandidate) { - if (peerConnection != null && peerConnection.getRemoteDescription() != null) { - peerConnection.addIceCandidate(iceCandidate); - } else { - iceCandidates.add(iceCandidate); - } - } - - public void sendNickChannelData(DataChannelMessageNick dataChannelMessage) { - ByteBuffer buffer; - if (magicDataChannel != null) { - try { - buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes()); - magicDataChannel.send(new DataChannel.Buffer(buffer, false)); - } catch (IOException e) { - Log.d(TAG, - "Failed to send channel data, attempting regular " + dataChannelMessage.toString()); - } - } - } - - public void sendChannelData(DataChannelMessage dataChannelMessage) { - ByteBuffer buffer; - if (magicDataChannel != null) { - try { - buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes()); - magicDataChannel.send(new DataChannel.Buffer(buffer, false)); - } catch (IOException e) { - Log.d(TAG, - "Failed to send channel data, attempting regular " + dataChannelMessage.toString()); - } - } - } - - public PeerConnection getPeerConnection() { - return peerConnection; - } - - public String getSessionId() { - return sessionId; - } - - public void setSessionId(String sessionId) { - this.sessionId = sessionId; - } - - public String getNick() { - if (!TextUtils.isEmpty(nick)) { - return nick; - } else { - return NextcloudTalkApplication.Companion.getSharedApplication() - .getString(R.string.nc_nick_guest); - } - } - - public void setNick(String nick) { - this.nick = nick; - } - - private void sendInitialMediaStatus() { - if (localMediaStream != null) { - if (localMediaStream.videoTracks.size() == 1 && localMediaStream.videoTracks.get(0) - .enabled()) { - sendChannelData(new DataChannelMessage("videoOn")); - } else { - sendChannelData(new DataChannelMessage("videoOff")); - } - - if (localMediaStream.audioTracks.size() == 1 && localMediaStream.audioTracks.get(0) - .enabled()) { - sendChannelData(new DataChannelMessage("audioOn")); - } else { - sendChannelData(new DataChannelMessage("audioOff")); - } - } - } - - public boolean isMCUPublisher() { - return isMCUPublisher; - } - - private void restartIce() { - if (connectionAttempts <= 5) { - if (!hasMCU || isMCUPublisher) { - MediaConstraints.KeyValuePair iceRestartConstraint = - new MediaConstraints.KeyValuePair("IceRestart", "true"); - - if (sdpConstraints.mandatory.contains(iceRestartConstraint)) { - sdpConstraints.mandatory.add(iceRestartConstraint); - } - - peerConnection.createOffer(magicSdpObserver, sdpConstraints); - } else { - // we have an MCU and this is not the publisher - // Do something if we have an MCU - } - - connectionAttempts++; - } - } - - public PeerConnection.IceConnectionState getPeerIceConnectionState() { - return peerIceConnectionState; - } - - private class MagicDataChannelObserver implements DataChannel.Observer { - - @Override - public void onBufferedAmountChange(long l) { - - } - - @Override - public void onStateChange() { - if (magicDataChannel != null && magicDataChannel.state().equals(DataChannel.State.OPEN) && - magicDataChannel.label().equals("status")) { - sendInitialMediaStatus(); - } - } - - @Override - public void onMessage(DataChannel.Buffer buffer) { - if (buffer.binary) { - Log.d(TAG, "Received binary msg over " + TAG + " " + sessionId); - return; - } - - ByteBuffer data = buffer.data; - final byte[] bytes = new byte[data.capacity()]; - data.get(bytes); - String strData = new String(bytes); - Log.d(TAG, "Got msg: " + strData + " over " + TAG + " " + sessionId); - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "Got msg: " + strData + " over " + peerConnection.hashCode() + " " + sessionId); - - try { - DataChannelMessage dataChannelMessage = - LoganSquare.parse(strData, DataChannelMessage.class); - - String internalNick; - if ("nickChanged".equals(dataChannelMessage.getType())) { - if (dataChannelMessage.getPayload() instanceof String) { - internalNick = (String) dataChannelMessage.getPayload(); - if (!internalNick.equals(nick)) { - setNick(nick); - EventBus.getDefault() - .post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType - .NICK_CHANGE, sessionId, getNick(), null, videoStreamType)); - } - } else { - if (dataChannelMessage.getPayload() != null) { - HashMap payloadHashMap = - (HashMap) dataChannelMessage.getPayload(); - EventBus.getDefault() - .post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType - .NICK_CHANGE, payloadHashMap.get("userid"), payloadHashMap.get("name"), null, - videoStreamType)); - } - } - } else if ("audioOn".equals(dataChannelMessage.getType())) { - remoteAudioOn = true; - EventBus.getDefault() - .post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType - .AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType)); - } else if ("audioOff".equals(dataChannelMessage.getType())) { - remoteAudioOn = false; - EventBus.getDefault() - .post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType - .AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType)); - } else if ("videoOn".equals(dataChannelMessage.getType())) { - remoteVideoOn = true; - EventBus.getDefault() - .post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType - .VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType)); - } else if ("videoOff".equals(dataChannelMessage.getType())) { - remoteVideoOn = false; - EventBus.getDefault() - .post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType - .VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType)); - } - } catch (IOException e) { - Log.d(TAG, "Failed to parse data channel message"); - } - } - } - - private class MagicPeerConnectionObserver implements PeerConnection.Observer { - private final String TAG = "MagicPeerConnectionObserver"; - - @Override - public void onSignalingChange(PeerConnection.SignalingState signalingState) { - } - - @Override - public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { - peerIceConnectionState = iceConnectionState; - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "iceConnectionChangeTo: " - + iceConnectionState.name() - + " over " - + peerConnection.hashCode() - + " " - + sessionId); - - Log.d("iceConnectionChangeTo: ", - iceConnectionState.name() + " over " + peerConnection.hashCode() + " " + sessionId); - if (iceConnectionState.equals(PeerConnection.IceConnectionState.CONNECTED)) { - connectionAttempts = 0; - /*EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType - .PEER_CONNECTED, sessionId, null, null));*/ - - if (!isMCUPublisher) { - EventBus.getDefault() - .post(new MediaStreamEvent(remoteMediaStream, sessionId, videoStreamType)); - } - - if (hasInitiated) { - sendInitialMediaStatus(); - } - } else if (iceConnectionState.equals(PeerConnection.IceConnectionState.CLOSED)) { - EventBus.getDefault() - .post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType - .PEER_CLOSED, sessionId, null, null, videoStreamType)); - connectionAttempts = 0; - } else if (iceConnectionState.equals(PeerConnection.IceConnectionState.FAILED)) { - /*if (MerlinTheWizard.isConnectedToInternet() && connectionAttempts < 5) { - restartIce(); - }*/ - if (isMCUPublisher) { - EventBus.getDefault() - .post(new PeerConnectionEvent( - PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED, sessionId, null, - null, null)); - } - } - } - - @Override - public void onIceConnectionReceivingChange(boolean b) { - - } - - @Override - public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) { - - } - - @Override - public void onIceCandidate(IceCandidate iceCandidate) { - NCIceCandidate ncIceCandidate = new NCIceCandidate(); - ncIceCandidate.setSdpMid(iceCandidate.sdpMid); - ncIceCandidate.setSdpMLineIndex(iceCandidate.sdpMLineIndex); - ncIceCandidate.setCandidate(iceCandidate.sdp); - EventBus.getDefault().post(new SessionDescriptionSendEvent(null, sessionId, - "candidate", ncIceCandidate, videoStreamType)); - } - - @Override - public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) { - - } - - @Override - public void onAddStream(MediaStream mediaStream) { - remoteMediaStream = mediaStream; - } - - @Override - public void onRemoveStream(MediaStream mediaStream) { - if (!isMCUPublisher) { - EventBus.getDefault().post(new MediaStreamEvent(null, sessionId, videoStreamType)); - } - } - - @Override - public void onDataChannel(DataChannel dataChannel) { - if (dataChannel.label().equals("status")) { - magicDataChannel = dataChannel; - magicDataChannel.registerObserver(new MagicDataChannelObserver()); - } - } - - @Override - public void onRenegotiationNeeded() { - - } - - @Override - public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) { - } - } - - private class MagicSdpObserver implements SdpObserver { - private final String TAG = "MagicSdpObserver"; - - @Override - public void onCreateFailure(String s) { - Log.d(TAG, s); - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "SDPObserver createFailure: " - + s - + " over " - + peerConnection.hashCode() - + " " - + sessionId); - } - - @Override - public void onSetFailure(String s) { - Log.d(TAG, s); - LoggingUtils.INSTANCE.writeLogEntryToFile(context, - "SDPObserver setFailure: " + s + " over " + peerConnection.hashCode() + " " + sessionId); - } - - @Override - public void onCreateSuccess(SessionDescription sessionDescription) { - SessionDescription sessionDescriptionWithPreferredCodec; - String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec - (sessionDescription.description, - "H264", false); - sessionDescriptionWithPreferredCodec = new SessionDescription( - sessionDescription.type, - sessionDescriptionStringWithPreferredCodec); - - EventBus.getDefault() - .post(new SessionDescriptionSendEvent(sessionDescriptionWithPreferredCodec, sessionId, - sessionDescription.type.canonicalForm().toLowerCase(), null, videoStreamType)); - - if (peerConnection != null) { - peerConnection.setLocalDescription(magicSdpObserver, sessionDescriptionWithPreferredCodec); - } - } - - @Override - public void onSetSuccess() { - if (peerConnection != null) { - if (peerConnection.getLocalDescription() == null) { - peerConnection.createAnswer(magicSdpObserver, sdpConstraints); - } - - if (peerConnection.getRemoteDescription() != null) { - drainIceCandidates(); - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.kt b/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.kt new file mode 100644 index 000000000..00ce7fc12 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.kt @@ -0,0 +1,395 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.nextcloud.talk.webrtc + +import android.annotation.SuppressLint +import android.content.Context +import android.text.TextUtils +import android.util.Log +import com.bluelinelabs.logansquare.LoganSquare +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.events.MediaStreamEvent +import com.nextcloud.talk.events.PeerConnectionEvent +import com.nextcloud.talk.events.SessionDescriptionSendEvent +import com.nextcloud.talk.events.WebSocketCommunicationEvent +import com.nextcloud.talk.models.json.signaling.DataChannelMessage +import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick +import com.nextcloud.talk.models.json.signaling.NCIceCandidate +import com.nextcloud.talk.utils.LoggingUtils.writeLogEntryToFile +import org.greenrobot.eventbus.EventBus +import org.koin.core.KoinComponent +import org.koin.core.inject +import org.webrtc.* +import org.webrtc.PeerConnection.* +import java.io.IOException +import java.nio.ByteBuffer +import java.util.* + +class MagicPeerConnectionWrapper(peerConnectionFactory: PeerConnectionFactory, + iceServerList: List?, + sdpConstraints: MediaConstraints, + sessionId: String, localSession: String?, mediaStream: MediaStream?, + isMCUPublisher: Boolean, hasMCU: Boolean, videoStreamType: String): KoinComponent { + val context: Context by inject() + private var iceCandidates: MutableList = ArrayList() + var peerConnection: PeerConnection? + private set + var sessionId: String + private var nick: String? = null + private val sdpConstraints: MediaConstraints + private var magicDataChannel: DataChannel? = null + val magicSdpObserver: MagicSdpObserver + private var remoteMediaStream: MediaStream? = null + private var remoteVideoOn = false + private var remoteAudioOn = false + private val hasInitiated: Boolean + private val localMediaStream: MediaStream? + val isMCUPublisher: Boolean + private val hasMCU: Boolean + val videoStreamType: String + private var connectionAttempts = 0 + var peerIceConnectionState: IceConnectionState? = null + private set + + fun removePeerConnection() { + if (magicDataChannel != null) { + magicDataChannel!!.dispose() + magicDataChannel = null + } + if (peerConnection != null) { + if (localMediaStream != null) { + peerConnection!!.removeStream(localMediaStream) + } + peerConnection!!.close() + peerConnection = null + } + } + + fun drainIceCandidates() { + if (peerConnection != null) { + for (iceCandidate in iceCandidates) { + peerConnection!!.addIceCandidate(iceCandidate) + } + iceCandidates = ArrayList() + } + } + + fun addCandidate(iceCandidate: IceCandidate) { + if (peerConnection != null && peerConnection!!.remoteDescription != null) { + peerConnection!!.addIceCandidate(iceCandidate) + } else { + iceCandidates.add(iceCandidate) + } + } + + @SuppressLint("LongLogTag") + fun sendNickChannelData(dataChannelMessage: DataChannelMessageNick) { + val buffer: ByteBuffer + if (magicDataChannel != null) { + try { + buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).toByteArray()) + magicDataChannel!!.send(DataChannel.Buffer(buffer, false)) + } catch (e: IOException) { + Log.d(TAG, + "Failed to send channel data, attempting regular $dataChannelMessage") + } + } + } + + @SuppressLint("LongLogTag") + fun sendChannelData(dataChannelMessage: DataChannelMessage) { + val buffer: ByteBuffer + if (magicDataChannel != null) { + try { + buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).toByteArray()) + magicDataChannel!!.send(DataChannel.Buffer(buffer, false)) + } catch (e: IOException) { + Log.d(TAG, + "Failed to send channel data, attempting regular $dataChannelMessage") + } + } + } + + fun getNick(): String? { + return if (!TextUtils.isEmpty(nick)) { + nick + } else { + context.resources.getString(R.string.nc_nick_guest) + } + } + + fun setNick(nick: String?) { + this.nick = nick + } + + private fun sendInitialMediaStatus() { + if (localMediaStream != null) { + if (localMediaStream.videoTracks.size == 1 && localMediaStream.videoTracks[0] + .enabled()) { + sendChannelData(DataChannelMessage("videoOn")) + } else { + sendChannelData(DataChannelMessage("videoOff")) + } + if (localMediaStream.audioTracks.size == 1 && localMediaStream.audioTracks[0] + .enabled()) { + sendChannelData(DataChannelMessage("audioOn")) + } else { + sendChannelData(DataChannelMessage("audioOff")) + } + } + } + + private fun restartIce() { + if (connectionAttempts <= 5) { + if (!hasMCU || isMCUPublisher) { + val iceRestartConstraint = MediaConstraints.KeyValuePair("IceRestart", "true") + if (sdpConstraints.mandatory.contains(iceRestartConstraint)) { + sdpConstraints.mandatory.add(iceRestartConstraint) + } + peerConnection!!.createOffer(magicSdpObserver, sdpConstraints) + } else { // we have an MCU and this is not the publisher +// Do something if we have an MCU + } + connectionAttempts++ + } + } + + private inner class MagicDataChannelObserver : DataChannel.Observer { + override fun onBufferedAmountChange(l: Long) {} + override fun onStateChange() { + if (magicDataChannel != null && magicDataChannel!!.state() == DataChannel.State.OPEN && magicDataChannel!!.label() == "status") { + sendInitialMediaStatus() + } + } + + @SuppressLint("LongLogTag") + override fun onMessage(buffer: DataChannel.Buffer) { + if (buffer.binary) { + Log.d(TAG, "Received binary msg over $TAG $sessionId") + return + } + val data = buffer.data + val bytes = ByteArray(data.capacity()) + data[bytes] + val strData = String(bytes) + Log.d(TAG, "Got msg: $strData over $TAG $sessionId") + writeLogEntryToFile(context, + "Got msg: " + strData + " over " + peerConnection.hashCode() + " " + sessionId) + try { + val dataChannelMessage = LoganSquare.parse(strData, DataChannelMessage::class.java) + val internalNick: String + if ("nickChanged" == dataChannelMessage.type) { + if (dataChannelMessage.payload is String) { + internalNick = dataChannelMessage.payload as String + if (internalNick != nick) { + setNick(nick) + EventBus.getDefault() + .post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.NICK_CHANGE, sessionId, getNick(), null, videoStreamType)) + } + } else { + if (dataChannelMessage.payload != null) { + val payloadHashMap = dataChannelMessage.payload as HashMap + EventBus.getDefault() + .post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.NICK_CHANGE, payloadHashMap["userid"], payloadHashMap["name"], null, + videoStreamType)) + } + } + } else if ("audioOn" == dataChannelMessage.type) { + remoteAudioOn = true + EventBus.getDefault() + .post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType)) + } else if ("audioOff" == dataChannelMessage.type) { + remoteAudioOn = false + EventBus.getDefault() + .post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType)) + } else if ("videoOn" == dataChannelMessage.type) { + remoteVideoOn = true + EventBus.getDefault() + .post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType)) + } else if ("videoOff" == dataChannelMessage.type) { + remoteVideoOn = false + EventBus.getDefault() + .post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType)) + } + } catch (e: IOException) { + Log.d(TAG, "Failed to parse data channel message") + } + } + } + + private inner class MagicPeerConnectionObserver : PeerConnection.Observer { + private val TAG = "MagicPeerConnectionObserver" + override fun onSignalingChange(signalingState: SignalingState) {} + override fun onIceConnectionChange(iceConnectionState: IceConnectionState) { + peerIceConnectionState = iceConnectionState + writeLogEntryToFile(context!!, + "iceConnectionChangeTo: " + + iceConnectionState.name + + " over " + + peerConnection.hashCode() + + " " + + sessionId) + Log.d("iceConnectionChangeTo: ", + iceConnectionState.name + " over " + peerConnection.hashCode() + " " + sessionId) + if (iceConnectionState == IceConnectionState.CONNECTED) { + connectionAttempts = 0 + /*EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType + .PEER_CONNECTED, sessionId, null, null));*/if (!isMCUPublisher) { + EventBus.getDefault() + .post(MediaStreamEvent(remoteMediaStream, sessionId, videoStreamType)) + } + if (hasInitiated) { + sendInitialMediaStatus() + } + } else if (iceConnectionState == IceConnectionState.CLOSED) { + EventBus.getDefault() + .post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PEER_CLOSED, sessionId, null, null, videoStreamType)) + connectionAttempts = 0 + } else if (iceConnectionState == IceConnectionState.FAILED) { /*if (MerlinTheWizard.isConnectedToInternet() && connectionAttempts < 5) { + restartIce(); + }*/ + if (isMCUPublisher) { + EventBus.getDefault() + .post(PeerConnectionEvent( + PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED, sessionId, null, + null, null)) + } + } + } + + override fun onIceConnectionReceivingChange(b: Boolean) {} + override fun onIceGatheringChange(iceGatheringState: IceGatheringState) {} + override fun onIceCandidate(iceCandidate: IceCandidate) { + val ncIceCandidate = NCIceCandidate() + ncIceCandidate.sdpMid = iceCandidate.sdpMid + ncIceCandidate.sdpMLineIndex = iceCandidate.sdpMLineIndex + ncIceCandidate.candidate = iceCandidate.sdp + EventBus.getDefault().post(SessionDescriptionSendEvent(null, sessionId, + "candidate", ncIceCandidate, videoStreamType)) + } + + override fun onIceCandidatesRemoved(iceCandidates: Array) {} + override fun onAddStream(mediaStream: MediaStream) { + remoteMediaStream = mediaStream + } + + override fun onRemoveStream(mediaStream: MediaStream) { + if (!isMCUPublisher) { + EventBus.getDefault().post(MediaStreamEvent(null, sessionId, videoStreamType)) + } + } + + override fun onDataChannel(dataChannel: DataChannel) { + if (dataChannel.label() == "status") { + magicDataChannel = dataChannel + magicDataChannel!!.registerObserver(MagicDataChannelObserver()) + } + } + + override fun onRenegotiationNeeded() {} + override fun onAddTrack(rtpReceiver: RtpReceiver, mediaStreams: Array) {} + } + + inner class MagicSdpObserver : SdpObserver { + private val TAG = "MagicSdpObserver" + override fun onCreateFailure(s: String) { + Log.d(TAG, s) + writeLogEntryToFile(context!!, + "SDPObserver createFailure: " + + s + + " over " + + peerConnection.hashCode() + + " " + + sessionId) + } + + override fun onSetFailure(s: String) { + Log.d(TAG, s) + writeLogEntryToFile(context!!, + "SDPObserver setFailure: " + s + " over " + peerConnection.hashCode() + " " + sessionId) + } + + override fun onCreateSuccess(sessionDescription: SessionDescription) { + val sessionDescriptionWithPreferredCodec: SessionDescription + val sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec(sessionDescription.description, + "H264", false) + sessionDescriptionWithPreferredCodec = SessionDescription( + sessionDescription.type, + sessionDescriptionStringWithPreferredCodec) + EventBus.getDefault() + .post(SessionDescriptionSendEvent(sessionDescriptionWithPreferredCodec, sessionId, + sessionDescription.type.canonicalForm().toLowerCase(), null, videoStreamType)) + if (peerConnection != null) { + peerConnection!!.setLocalDescription(magicSdpObserver, sessionDescriptionWithPreferredCodec) + } + } + + override fun onSetSuccess() { + if (peerConnection != null) { + if (peerConnection!!.localDescription == null) { + peerConnection!!.createAnswer(magicSdpObserver, sdpConstraints) + } + if (peerConnection!!.remoteDescription != null) { + drainIceCandidates() + } + } + } + } + + companion object { + private const val TAG = "MagicPeerConnectionWrapper" + } + + init { + localMediaStream = mediaStream + this.videoStreamType = videoStreamType + this.hasMCU = hasMCU + this.sessionId = sessionId + this.sdpConstraints = sdpConstraints + magicSdpObserver = MagicSdpObserver() + hasInitiated = sessionId.compareTo(localSession!!) < 0 + this.isMCUPublisher = isMCUPublisher + peerConnection = peerConnectionFactory.createPeerConnection(iceServerList, sdpConstraints, + MagicPeerConnectionObserver()) + if (peerConnection != null) { + if (localMediaStream != null) { + peerConnection!!.addStream(localMediaStream) + } + if (hasMCU || hasInitiated) { + val init = DataChannel.Init() + init.negotiated = false + magicDataChannel = peerConnection!!.createDataChannel("status", init) + magicDataChannel!!.registerObserver(MagicDataChannelObserver()) + if (isMCUPublisher) { + peerConnection!!.createOffer(magicSdpObserver, sdpConstraints) + } else if (hasMCU) { + val hashMap = HashMap() + hashMap["sessionId"] = sessionId + EventBus.getDefault() + .post(WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap)) + } else if (hasInitiated) { + peerConnection!!.createOffer(magicSdpObserver, sdpConstraints) + } + } + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 74524f662..e6f75d71c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,26 +1,6 @@ -# -# Nextcloud Talk application -# -# @author Mario Danic -# Copyright (C) 2017-2019 Mario Danic -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -#Thu Aug 22 11:56:51 CEST 2019 +#Wed Dec 11 12:48:55 CET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip