diff --git a/.idea/misc.xml b/.idea/misc.xml
index b36c7ce25..b3243728f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
index dd4cd1d1b..61d732101 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,11 +2,7 @@
-
-
-
-
-
+
diff --git a/app/build.gradle b/app/build.gradle
index 0599b80a6..5ff8c340f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -242,6 +242,8 @@ dependencies {
implementation 'com.afollestad.material-dialogs:bottomsheets:3.1.0'
implementation 'com.afollestad.material-dialogs:lifecycle:3.1.0'
+ implementation 'com.google.code.gson:gson:2.8.6'
+
testImplementation 'junit:junit:4.13'
testImplementation 'org.mockito:mockito-core:3.0.0'
testImplementation 'org.powermock:powermock-core:2.0.2'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f1d929427..494b5ab36 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,6 +51,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 75197b985..993174edd 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
@@ -25,6 +25,8 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
+import android.os.Handler
+import android.provider.ContactsContract
import android.text.TextUtils
import android.view.ViewGroup
import androidx.annotation.RequiresApi
@@ -37,17 +39,31 @@ import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import com.google.android.material.appbar.MaterialToolbar
+import com.google.android.material.snackbar.Snackbar
import com.nextcloud.talk.R
+import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.controllers.*
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
+import com.nextcloud.talk.models.json.conversations.RoomOverall
+import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ConductorRemapping
+import com.nextcloud.talk.utils.ConductorRemapping.remapChatController
import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
+import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.UserUtils
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
import io.requery.Persistable
import io.requery.android.sqlcipher.SqlCipherDatabaseSource
import io.requery.reactivex.ReactiveEntityStore
+import org.parceler.Parcels
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@@ -64,6 +80,8 @@ class MainActivity : BaseActivity(), ActionBarProvider {
lateinit var dataStore: ReactiveEntityStore
@Inject
lateinit var sqlCipherDatabaseSource: SqlCipherDatabaseSource
+ @Inject
+ lateinit var ncApi: NcApi
private var router: Router? = null
@@ -132,8 +150,91 @@ class MainActivity : BaseActivity(), ActionBarProvider {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkIfWeAreSecure()
}
+
+ handleActionFromContact(intent)
}
+ private fun handleActionFromContact(intent: Intent) {
+ if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
+
+ val cursor = contentResolver.query(intent.data!!, null, null, null, null)
+
+ var userId = ""
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ // userId @ server
+ userId = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1))
+ }
+
+ cursor.close()
+ }
+
+ when (intent.type) {
+ "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat" -> {
+ val user = userId.split("@")[0]
+ val baseUrl = userId.split("@")[1]
+ if (userUtils.currentUser?.baseUrl?.endsWith(baseUrl) == true) {
+ startConversation(user)
+ } else {
+ Snackbar.make(container, "Account not found", Snackbar.LENGTH_LONG).show()
+ }
+ }
+ }
+ }
+ }
+
+ private fun startConversation(userId: String) {
+ val roomType = "1"
+ val currentUser = userUtils.currentUser ?: return
+
+ val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
+ val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(currentUser.baseUrl, roomType,
+ userId, null)
+ ncApi.createRoom(credentials,
+ retrofitBucket.url, retrofitBucket.queryMap)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : Observer {
+ override fun onSubscribe(d: Disposable) {}
+ override fun onNext(roomOverall: RoomOverall) {
+ val conversationIntent = Intent(context, MagicCallActivity::class.java)
+ val bundle = Bundle()
+ bundle.putParcelable(KEY_USER_ENTITY, currentUser)
+ bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs.data.token)
+ bundle.putString(KEY_ROOM_ID, roomOverall.ocs.data.roomId)
+ if (currentUser.hasSpreedFeatureCapability("chat-v2")) {
+ ncApi.getRoom(credentials,
+ ApiUtils.getRoom(currentUser.baseUrl,
+ roomOverall.ocs.data.token))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : Observer {
+ override fun onSubscribe(d: Disposable) {}
+ override fun onNext(roomOverall: RoomOverall) {
+ bundle.putParcelable(KEY_ACTIVE_CONVERSATION,
+ Parcels.wrap(roomOverall.ocs.data))
+ remapChatController(router!!, currentUser.id,
+ roomOverall.ocs.data.token, bundle, true)
+ }
+
+ override fun onError(e: Throwable) {}
+ override fun onComplete() {}
+ })
+ } else {
+ conversationIntent.putExtras(bundle)
+ startActivity(conversationIntent)
+ Handler().postDelayed({
+ if (!isDestroyed) {
+ router!!.popCurrentController()
+ }
+ }, 100)
+ }
+ }
+
+ override fun onError(e: Throwable) {}
+ override fun onComplete() {}
+ })
+ }
@RequiresApi(api = Build.VERSION_CODES.M)
fun checkIfWeAreSecure() {
@@ -154,6 +255,8 @@ class MainActivity : BaseActivity(), ActionBarProvider {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
+ handleActionFromContact(intent)
+
if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
router!!.pushController(RouterTransaction.with(CallNotificationController(intent.extras))
diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java
index cb073fef4..f9750f4ee 100644
--- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java
+++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java
@@ -20,9 +20,10 @@
*/
package com.nextcloud.talk.api;
-import androidx.annotation.Nullable;
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
import com.nextcloud.talk.models.json.chat.ChatOverall;
+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 com.nextcloud.talk.models.json.generic.Status;
import com.nextcloud.talk.models.json.mention.MentionOverall;
@@ -30,19 +31,32 @@ import com.nextcloud.talk.models.json.notifications.NotificationOverall;
import com.nextcloud.talk.models.json.participants.AddParticipantOverall;
import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
import com.nextcloud.talk.models.json.push.PushRegistrationOverall;
-import com.nextcloud.talk.models.json.conversations.RoomOverall;
-import com.nextcloud.talk.models.json.conversations.RoomsOverall;
+import com.nextcloud.talk.models.json.search.ContactsByNumberOverall;
import com.nextcloud.talk.models.json.signaling.SignalingOverall;
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
-import io.reactivex.Observable;
-import okhttp3.ResponseBody;
-import retrofit2.Response;
-import retrofit2.http.*;
import java.util.List;
import java.util.Map;
+import androidx.annotation.Nullable;
+import io.reactivex.Observable;
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+import retrofit2.Response;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.Field;
+import retrofit2.http.FieldMap;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.Header;
+import retrofit2.http.POST;
+import retrofit2.http.PUT;
+import retrofit2.http.Query;
+import retrofit2.http.QueryMap;
+import retrofit2.http.Url;
+
public interface NcApi {
/*
@@ -327,4 +341,8 @@ public interface NcApi {
@Url String url, @Field("state") Integer state,
@Field("timer") Long timer);
+ @POST
+ Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization,
+ @Url String url,
+ @Body RequestBody search);
}
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
index 782371bd9..98bb83d78 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
+++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
@@ -29,23 +29,16 @@ import android.os.Bundle;
import android.os.Handler;
import android.text.InputType;
import android.text.TextUtils;
-import android.view.*;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.widget.SearchView;
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-import androidx.core.view.MenuItemCompat;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-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;
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat;
@@ -74,10 +67,11 @@ import com.nextcloud.talk.events.BottomSheetLockEvent;
import com.nextcloud.talk.events.EventStatus;
import com.nextcloud.talk.events.MoreMenuClickEvent;
import com.nextcloud.talk.interfaces.ConversationMenuInterface;
+import com.nextcloud.talk.jobs.ContactAddressBookWorker;
import com.nextcloud.talk.jobs.DeleteConversationWorker;
import com.nextcloud.talk.models.database.UserEntity;
-import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.models.json.conversations.Conversation;
+import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.ConductorRemapping;
import com.nextcloud.talk.utils.DisplayUtils;
@@ -88,6 +82,32 @@ import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.yarolegovich.lovelydialog.LovelySaveStateHandler;
import com.yarolegovich.lovelydialog.LovelyStandardDialog;
+
+import org.apache.commons.lang3.builder.CompareToBuilder;
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+import org.parceler.Parcels;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.SearchView;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.core.view.MenuItemCompat;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import androidx.work.Data;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+import autodagger.AutoInjector;
+import butterknife.BindView;
import eu.davidea.fastscroller.FastScroller;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
@@ -95,18 +115,8 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
-import org.apache.commons.lang3.builder.CompareToBuilder;
-import org.greenrobot.eventbus.EventBus;
-import org.greenrobot.eventbus.Subscribe;
-import org.greenrobot.eventbus.ThreadMode;
-import org.parceler.Parcels;
import retrofit2.HttpException;
-import javax.inject.Inject;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
@AutoInjector(NextcloudTalkApplication.class)
public class ConversationsListController extends BaseController implements SearchView.OnQueryTextListener,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FastScroller
@@ -442,6 +452,7 @@ public class ConversationsListController extends BaseController implements Searc
emptyLayoutView.setOnClickListener(v -> showNewConversationsScreen());
floatingActionButton.setOnClickListener(v -> {
+ ContactAddressBookWorker.Companion.run(context);
showNewConversationsScreen();
});
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java
index fdf801e31..07e57b46d 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java
+++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java
@@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -38,6 +39,7 @@ import android.view.WindowManager;
import android.widget.Checkable;
import android.widget.TextView;
+import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
@@ -45,12 +47,14 @@ import com.bluelinelabs.logansquare.LoganSquare;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView;
+import com.google.android.material.snackbar.Snackbar;
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.controllers.base.BaseController;
import com.nextcloud.talk.jobs.AccountRemovalWorker;
+import com.nextcloud.talk.jobs.ContactAddressBookWorker;
import com.nextcloud.talk.models.RingtoneSettings;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.utils.ApiUtils;
@@ -151,6 +155,8 @@ public class SettingsController extends BaseController {
MaterialSwitchPreference screenLockSwitchPreference;
@BindView(R.id.settings_screen_lock_timeout)
MaterialChoicePreference screenLockTimeoutChoicePreference;
+ @BindView(R.id.settings_phone_book_integration)
+ MaterialSwitchPreference phoneBookIntegretationPreference;
@BindView(R.id.message_text)
TextView messageText;
@@ -171,6 +177,7 @@ public class SettingsController extends BaseController {
private OnPreferenceValueChangedListener screenLockChangeListener;
private OnPreferenceValueChangedListener screenLockTimeoutChangeListener;
private OnPreferenceValueChangedListener themeChangeListener;
+ private OnPreferenceValueChangedListener phoneBookIntegrationChangeListener;
private Disposable profileQueryDisposable;
private Disposable dbQueryDisposable;
@@ -206,6 +213,8 @@ public class SettingsController extends BaseController {
appPreferences.registerScreenLockListener(screenLockChangeListener = new ScreenLockListener());
appPreferences.registerScreenLockTimeoutListener(screenLockTimeoutChangeListener = new ScreenLockTimeoutListener());
appPreferences.registerThemeChangeListener(themeChangeListener = new ThemeChangeListener());
+ appPreferences.registerPhoneBookIntegrationChangeListener(
+ phoneBookIntegrationChangeListener = new PhoneBookIntegrationChangeListener(this));
List listWithIntFields = new ArrayList<>();
listWithIntFields.add("proxy_port");
@@ -435,6 +444,7 @@ public class SettingsController extends BaseController {
}
((Checkable) linkPreviewsSwitchPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.getAreLinkPreviewsAllowed());
+ ((Checkable) phoneBookIntegretationPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.isPhoneBookIntegrationEnabled());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
@@ -645,6 +655,7 @@ public class SettingsController extends BaseController {
appPreferences.unregisterScreenLockListener(screenLockChangeListener);
appPreferences.unregisterScreenLockTimeoutListener(screenLockTimeoutChangeListener);
appPreferences.unregisterThemeChangeListener(themeChangeListener);
+ appPreferences.unregisterPhoneBookIntegrationChangeListener(phoneBookIntegrationChangeListener);
}
super.onDestroy();
}
@@ -707,6 +718,24 @@ public class SettingsController extends BaseController {
return getResources().getString(R.string.nc_settings);
}
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (requestCode == ContactAddressBookWorker.REQUEST_PERMISSION &&
+ grantResults.length > 0 &&
+ grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ WorkManager
+ .getInstance()
+ .enqueue(new OneTimeWorkRequest.Builder(ContactAddressBookWorker.class).build());
+ } else {
+ appPreferences.setPhoneBookIntegration(false);
+ ((Checkable) phoneBookIntegretationPreference.findViewById(R.id.mp_checkable)).setChecked(appPreferences.isPhoneBookIntegrationEnabled());
+ Snackbar.make(getView(),
+ context.getResources().getString(R.string.no_phone_book_integration_due_to_permissions),
+ Snackbar.LENGTH_LONG)
+ .show();
+ }
+ }
+
private class ScreenLockTimeoutListener implements OnPreferenceValueChangedListener {
@Override
@@ -797,4 +826,19 @@ public class SettingsController extends BaseController {
NextcloudTalkApplication.Companion.setAppTheme(newValue);
}
}
+
+ private class PhoneBookIntegrationChangeListener implements OnPreferenceValueChangedListener {
+ private final Controller controller;
+
+ public PhoneBookIntegrationChangeListener(Controller controller) {
+ this.controller = controller;
+ }
+
+ @Override
+ public void onChanged(Boolean newValue) {
+ if (newValue) {
+ ContactAddressBookWorker.Companion.checkPermission(controller, context);
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt
new file mode 100644
index 000000000..abd5c1e48
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt
@@ -0,0 +1,385 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ *
+ * 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.Manifest
+import android.accounts.Account
+import android.accounts.AccountManager
+import android.content.ContentProviderOperation
+import android.content.Context
+import android.content.OperationApplicationException
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.RemoteException
+import android.provider.ContactsContract
+import android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER
+import android.util.Log
+import androidx.core.content.ContextCompat
+import androidx.core.os.ConfigurationCompat
+import androidx.work.*
+import autodagger.AutoInjector
+import com.bluelinelabs.conductor.Controller
+import com.google.gson.Gson
+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.search.ContactsByNumberOverall
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.database.user.UserUtils
+import com.nextcloud.talk.utils.preferences.AppPreferences
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import okhttp3.MediaType
+import okhttp3.RequestBody
+import javax.inject.Inject
+import com.nextcloud.talk.R
+
+
+@AutoInjector(NextcloudTalkApplication::class)
+class ContactAddressBookWorker(val context: Context, workerParameters: WorkerParameters) :
+ Worker(context, workerParameters) {
+
+ @Inject
+ lateinit var ncApi: NcApi
+
+ @Inject
+ lateinit var userUtils: UserUtils
+
+ @Inject
+ lateinit var appPreferences: AppPreferences
+
+ override fun doWork(): Result {
+ sharedApplication!!.componentApplication.inject(this)
+
+ val currentUser = userUtils.currentUser
+
+ if (currentUser == null) {
+ Log.e(javaClass.simpleName, "No current user!")
+ return Result.failure()
+ }
+ // Check if run already at the date
+ val force = inputData.getBoolean(KEY_FORCE, false)
+ if (!force) {
+ if (System.currentTimeMillis() - appPreferences.getPhoneBookIntegrationLastRun(0L) < 24 * 60 * 60 * 1000) {
+ Log.d(TAG, "Already run within last 24h")
+ return Result.success()
+ }
+ }
+
+ AccountManager.get(context).addAccountExplicitly(Account(ACCOUNT_NAME, ACCOUNT_TYPE), "", null)
+
+ // collect all contacts with phone number
+ val contactsWithNumbers = collectPhoneNumbers()
+
+ val currentLocale = ConfigurationCompat.getLocales(context.resources.configuration)[0].country
+
+ val map = mutableMapOf()
+ map["location"] = currentLocale
+ map["search"] = contactsWithNumbers
+
+ val json = Gson().toJson(map)
+
+ ncApi.searchContactsByPhoneNumber(
+ ApiUtils.getCredentials(currentUser.username, currentUser.token),
+ ApiUtils.getUrlForSearchByNumber(currentUser.baseUrl),
+ RequestBody.create(MediaType.parse("application/json"), json))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(object : Observer {
+ override fun onComplete() {
+ }
+
+ override fun onSubscribe(d: Disposable) {
+ }
+
+ override fun onNext(foundContacts: ContactsByNumberOverall) {
+ Log.d(javaClass.simpleName, "next")
+
+ // todo update
+ up(foundContacts)
+ }
+
+ override fun onError(e: Throwable) {
+ // TODO error handling
+ Log.d(javaClass.simpleName, "error")
+ }
+
+ })
+
+ // store timestamp
+ appPreferences.setPhoneBookIntegrationLastRun(System.currentTimeMillis())
+
+ return Result.success()
+ }
+
+ private fun collectPhoneNumbers(): MutableMap> {
+ val result: MutableMap> = mutableMapOf()
+
+ val contactCursor = context.contentResolver.query(
+ ContactsContract.Contacts.CONTENT_URI,
+ null,
+ null,
+ null,
+ null
+ )
+
+ if (contactCursor != null) {
+ if (contactCursor.count > 0) {
+ contactCursor.moveToFirst()
+ for (i in 0 until contactCursor.count) {
+ val numbers: MutableList = mutableListOf()
+
+ val id = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts._ID))
+ val lookup = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY))
+
+ val phonesCursor = context.contentResolver.query(
+ ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
+ null,
+ ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + id,
+ null,
+ null)
+
+ if (phonesCursor != null) {
+ while (phonesCursor.moveToNext()) {
+ numbers.add(phonesCursor.getString(phonesCursor.getColumnIndex(NUMBER)))
+ }
+
+ result[lookup] = numbers
+
+ phonesCursor.close()
+ }
+
+ contactCursor.moveToNext()
+ }
+ }
+
+ contactCursor.close()
+ }
+
+ return result
+ }
+
+ private fun up(foundContacts: ContactsByNumberOverall) {
+ val map = foundContacts.ocs.map
+
+ // Delete all old associations (those that are associated on phone, but not in server response)
+ val rawContactUri = ContactsContract.Data.CONTENT_URI
+ .buildUpon()
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, "Nextcloud Talk")
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, "com.nextcloud.talk2")
+ .appendQueryParameter(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
+ .build()
+
+ // get all raw contacts
+ val rawContactsCursor = context.contentResolver.query(
+ rawContactUri,
+ null,
+ null,
+ null,
+ null
+ )
+
+ if (rawContactsCursor != null) {
+ if (rawContactsCursor.count > 0) {
+ while (rawContactsCursor.moveToNext()) {
+ val id = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.RawContacts._ID))
+ val sync1 = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.SYNC1))
+ val lookupKey = rawContactsCursor.getString(rawContactsCursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY))
+ Log.d("Contact", "Found associated: $id")
+
+ if (map == null || !map.containsKey(lookupKey)) {
+ if (sync1 != null) {
+ deleteAssociation(sync1)
+ }
+ }
+ }
+ }
+
+ rawContactsCursor.close()
+ }
+
+ // update / change found
+ if (map != null && map.isNotEmpty()) {
+ for (contact in foundContacts.ocs.map) {
+ val lookupKey = contact.key
+ val cloudId = contact.value
+
+ update(lookupKey, cloudId)
+ }
+ }
+ }
+
+ private fun update(uniqueId: String, cloudId: String) {
+ val lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, uniqueId)
+ val lookupContactUri = ContactsContract.Contacts.lookupContact(context.contentResolver, lookupUri)
+ val contactCursor = context.contentResolver.query(
+ lookupContactUri,
+ null,
+ null,
+ null,
+ null)
+
+ if (contactCursor != null) {
+ if (contactCursor.count > 0) {
+ contactCursor.moveToFirst()
+
+ val id = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts._ID))
+
+ val phonesCursor = context.contentResolver.query(
+ ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
+ null,
+ ContactsContract.Data.CONTACT_ID + " = " + id,
+ null,
+ null)
+
+ val numbers = mutableListOf()
+ if (phonesCursor != null) {
+ while (phonesCursor.moveToNext()) {
+ numbers.add(phonesCursor.getString(phonesCursor.getColumnIndex(NUMBER)))
+ }
+
+ phonesCursor.close()
+ }
+
+ var displayName: String? = null
+
+ val whereName = ContactsContract.Data.MIMETYPE + " = ? AND " + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?"
+ val whereNameParams = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id)
+ val nameCursor = context.contentResolver.query(
+ ContactsContract.Data.CONTENT_URI,
+ null,
+ whereName,
+ whereNameParams,
+ ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
+ if (nameCursor != null) {
+ while (nameCursor.moveToNext()) {
+ displayName = nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME))
+ }
+ nameCursor.close()
+ }
+
+ if (displayName == null) {
+ return
+ }
+
+ // update entries
+ val ops = ArrayList()
+ val rawContactsUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon()
+ .build()
+ val dataUri = ContactsContract.Data.CONTENT_URI.buildUpon()
+ .build()
+
+ ops.add(ContentProviderOperation
+ .newInsert(rawContactsUri)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, ACCOUNT_NAME)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE)
+ .withValue(ContactsContract.RawContacts.AGGREGATION_MODE,
+ ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
+ .withValue(ContactsContract.RawContacts.SYNC2, cloudId)
+ .build())
+ ops.add(ContentProviderOperation
+ .newInsert(dataUri)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+ .withValue(NUMBER, numbers[0])
+ .build())
+ ops.add(ContentProviderOperation
+ .newInsert(dataUri)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
+ .build())
+ ops.add(ContentProviderOperation
+ .newInsert(dataUri)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat")
+ .withValue(ContactsContract.Data.DATA1, cloudId)
+ .withValue(ContactsContract.Data.DATA2, "Chat via " + context.resources.getString(R.string.nc_app_name))
+ .build())
+
+ try {
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
+ } catch (e: OperationApplicationException) {
+ e.printStackTrace()
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ }
+ }
+
+ contactCursor.close()
+ }
+ }
+
+ private fun deleteAssociation(id: String) {
+ Log.d("Contact", "Delete associated: $id")
+
+ val rawContactUri = ContactsContract.RawContacts.CONTENT_URI
+ .buildUpon()
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, "Nextcloud Talk")
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, "com.nextcloud.talk2")
+ .build()
+
+
+ val count = context.contentResolver.delete(rawContactUri, ContactsContract.RawContacts.SYNC2 + " LIKE \"" + id + "\"", null)
+ Log.d("Contact", "deleted $count for id $id")
+ }
+
+ companion object {
+ const val TAG = "ContactAddressBook"
+ const val REQUEST_PERMISSION = 231
+ const val KEY_FORCE = "KEY_FORCE"
+ const val ACCOUNT_TYPE = "com.nextcloud.talk2"
+ const val ACCOUNT_NAME = "Nextcloud Talk"
+
+ fun run(context: Context) {
+ if (ContextCompat.checkSelfPermission(context,
+ Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED &&
+ ContextCompat.checkSelfPermission(context,
+ Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
+ WorkManager
+ .getInstance()
+ .enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java)
+ .setInputData(Data.Builder().putBoolean(KEY_FORCE, false).build())
+ .build())
+ }
+ }
+
+ fun checkPermission(controller: Controller, context: Context) {
+ if (ContextCompat.checkSelfPermission(context,
+ Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED ||
+ ContextCompat.checkSelfPermission(context,
+ Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+ controller.requestPermissions(arrayOf(Manifest.permission.WRITE_CONTACTS,
+ Manifest.permission.READ_CONTACTS), REQUEST_PERMISSION)
+ } else {
+ WorkManager
+ .getInstance()
+ .enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java)
+ .setInputData(Data.Builder().putBoolean(KEY_FORCE, true).build())
+ .build())
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/search/ContactsByNumberOCS.java b/app/src/main/java/com/nextcloud/talk/models/json/search/ContactsByNumberOCS.java
new file mode 100644
index 000000000..e0f788555
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/models/json/search/ContactsByNumberOCS.java
@@ -0,0 +1,40 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ *
+ * 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.models.json.search;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+import com.nextcloud.talk.models.json.generic.GenericOCS;
+
+import org.parceler.Parcel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import lombok.Data;
+
+@Data
+@Parcel
+@JsonObject
+public class ContactsByNumberOCS extends GenericOCS {
+ @JsonField(name = "data")
+ public Map map = new HashMap();
+}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/search/ContactsByNumberOverall.java b/app/src/main/java/com/nextcloud/talk/models/json/search/ContactsByNumberOverall.java
new file mode 100644
index 000000000..8ec10ebe1
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/models/json/search/ContactsByNumberOverall.java
@@ -0,0 +1,36 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ *
+ * 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.models.json.search;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+
+import org.parceler.Parcel;
+
+import lombok.Data;
+
+@Data
+@Parcel
+@JsonObject
+public class ContactsByNumberOverall {
+ @JsonField(name = "ocs")
+ public ContactsByNumberOCS ocs;
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
index 17ffc771b..4f0447727 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
@@ -21,17 +21,20 @@ package com.nextcloud.talk.utils;
import android.net.Uri;
import android.text.TextUtils;
-import androidx.annotation.DimenRes;
+
import com.nextcloud.talk.BuildConfig;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.RetrofitBucket;
-import okhttp3.Credentials;
-import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
+import javax.annotation.Nullable;
+
+import androidx.annotation.DimenRes;
+import okhttp3.Credentials;
+
public class ApiUtils {
private static String ocsApiVersion = "/ocs/v2.php";
private static String spreedApiVersion = "/apps/spreed/api/v1";
@@ -276,4 +279,8 @@ public class ApiUtils {
public static String getUrlForReadOnlyState(String baseUrl, String roomToken) {
return baseUrl + ocsApiVersion + spreedApiVersion + "/room/" + roomToken + "/read-only";
}
+
+ public static String getUrlForSearchByNumber(String baseUrl) {
+ return baseUrl + ocsApiVersion + "/cloud/users/search/by-phone";
+ }
}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/AuthenticatorService.java b/app/src/main/java/com/nextcloud/talk/utils/AuthenticatorService.java
new file mode 100644
index 000000000..aeb8872ec
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/AuthenticatorService.java
@@ -0,0 +1,104 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ *
+ * 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;
+
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+public class AuthenticatorService extends Service {
+
+ private static class Authenticator extends AbstractAccountAuthenticator {
+ public Authenticator(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) {
+ return null;
+ }
+
+ @Override
+ public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account) throws NetworkErrorException {
+ return super.getAccountRemovalAllowed(response, account);
+ }
+
+ @Override
+ public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+ return null;
+ }
+
+ @Override
+ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
+ throws NetworkErrorException {
+ return null;
+ }
+
+ @Override
+ public String getAuthTokenLabel(String authTokenType) {
+ return null;
+ }
+
+ @Override
+ public Bundle hasFeatures(AccountAuthenticatorResponse response,
+ Account account, String[] features) {
+ return null;
+ }
+
+ @Override
+ public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) {
+ return null;
+ }
+
+ }
+
+ private static Authenticator authenticator = null;
+
+ protected Authenticator getAuthenticator() {
+ if (authenticator == null) {
+ authenticator = new Authenticator(this);
+ }
+ return authenticator;
+ }
+
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
+ return getAuthenticator().getIBinder();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/SyncAdapter.java b/app/src/main/java/com/nextcloud/talk/utils/SyncAdapter.java
new file mode 100644
index 000000000..397b49d34
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/SyncAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ *
+ * 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;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.util.Log;
+
+class SyncAdapter extends AbstractThreadedSyncAdapter {
+
+ public SyncAdapter(Context context, boolean autoInitialize) {
+ super(context, autoInitialize);
+ Log.i("SyncAdapter", "Sync adapter created");
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult syncResult) {
+ Log.i("SyncAdapter", "Sync adapter called");
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/SyncService.java b/app/src/main/java/com/nextcloud/talk/utils/SyncService.java
new file mode 100644
index 000000000..87be50d32
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/utils/SyncService.java
@@ -0,0 +1,50 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ *
+ * 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;
+
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+public class SyncService extends Service {
+
+ private static final Object sSyncAdapterLock = new Object();
+
+ private static SyncAdapter sSyncAdapter = null;
+
+ @Override
+ public void onCreate() {
+ Log.i("SyncService", "Sync service created");
+ synchronized (sSyncAdapterLock) {
+ if (sSyncAdapter == null) {
+ sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
+ }
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i("SyncService", "Sync service binded");
+ return sSyncAdapter.getSyncAdapterBinder();
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/UserUtils.java b/app/src/main/java/com/nextcloud/talk/utils/database/user/UserUtils.java
index bf6b174ad..d0a7aeffb 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/database/user/UserUtils.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/UserUtils.java
@@ -21,9 +21,13 @@
package com.nextcloud.talk.utils.database.user;
import android.text.TextUtils;
-import androidx.annotation.Nullable;
+
import com.nextcloud.talk.models.database.User;
import com.nextcloud.talk.models.database.UserEntity;
+
+import java.util.List;
+
+import androidx.annotation.Nullable;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@@ -32,8 +36,6 @@ import io.requery.Persistable;
import io.requery.query.Result;
import io.requery.reactivex.ReactiveEntityStore;
-import java.util.List;
-
public class UserUtils {
private ReactiveEntityStore dataStore;
@@ -76,7 +78,7 @@ public class UserUtils {
return null;
}
- public UserEntity getCurrentUser() {
+ public @Nullable UserEntity getCurrentUser() {
Result findUserQueryResult = dataStore.select(User.class).where(UserEntity.CURRENT.eq(true)
.and(UserEntity.SCHEDULED_FOR_DELETION.notEqual(true)))
.limit(1).get();
diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
index 455da289b..f6e4679eb 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
@@ -238,6 +238,13 @@ public interface AppPreferences {
@KeyByString("link_previews")
@DefaultValue(R.bool.value_true)
boolean getAreLinkPreviewsAllowed();
+
+ @KeyByString("phone_book_integration")
+ @DefaultValue(R.bool.value_false)
+ boolean isPhoneBookIntegrationEnabled();
+
+ @KeyByString("phone_book_integration")
+ void setPhoneBookIntegration(boolean value);
@KeyByString("link_previews")
void setLinkPreviewsAllowed(boolean value);
@@ -280,6 +287,20 @@ public interface AppPreferences {
@KeyByResource(R.string.nc_settings_theme_key)
@UnregisterChangeListenerMethod
void unregisterThemeChangeListener(OnPreferenceValueChangedListener listener);
+
+ @KeyByResource(R.string.nc_settings_phone_book_integration_key)
+ @RegisterChangeListenerMethod
+ void registerPhoneBookIntegrationChangeListener(OnPreferenceValueChangedListener listener);
+
+ @KeyByResource(R.string.nc_settings_phone_book_integration_key)
+ @UnregisterChangeListenerMethod
+ void unregisterPhoneBookIntegrationChangeListener(OnPreferenceValueChangedListener listener);
+
+ @KeyByString("phone_book_integration_last_run")
+ void setPhoneBookIntegrationLastRun(long currentTimeMillis);
+
+ @KeyByString("phone_book_integration_last_run")
+ long getPhoneBookIntegrationLastRun(Long defaultValue);
@ClearMethod
void clear();
diff --git a/app/src/main/res/layout/controller_settings.xml b/app/src/main/res/layout/controller_settings.xml
index 5fe68dde5..a7a09c086 100644
--- a/app/src/main/res/layout/controller_settings.xml
+++ b/app/src/main/res/layout/controller_settings.xml
@@ -217,6 +217,15 @@
apc:mp_key="@string/nc_settings_link_previews_key"
apc:mp_summary="@string/nc_settings_link_previews_desc"
apc:mp_title="@string/nc_settings_link_previews_title" />
+
+
M3.27,4.27L19.74,20.74
+ phone_book_integration
+ Match contacts based on phone number to integrate Talk shortcut in phone book
+ Phone book integration
+ No phone book integration due to missing permissions
diff --git a/app/src/main/res/xml/auth.xml b/app/src/main/res/xml/auth.xml
new file mode 100644
index 000000000..7d60d273c
--- /dev/null
+++ b/app/src/main/res/xml/auth.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/app/src/main/res/xml/contacts.xml b/app/src/main/res/xml/contacts.xml
new file mode 100644
index 000000000..830443fc1
--- /dev/null
+++ b/app/src/main/res/xml/contacts.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/syncadapter.xml b/app/src/main/res/xml/syncadapter.xml
new file mode 100644
index 000000000..940c3419c
--- /dev/null
+++ b/app/src/main/res/xml/syncadapter.xml
@@ -0,0 +1,25 @@
+
+
+
+