mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-28 09:38:14 +03:00
Add phone book integration
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
8d53e71547
commit
ad97abaee4
22 changed files with 1043 additions and 47 deletions
|
@ -5,7 +5,7 @@
|
|||
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="12">
|
||||
<list size="15">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
|
@ -18,12 +18,15 @@
|
|||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||
<item index="10" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
||||
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
||||
<item index="13" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
|
||||
<item index="14" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="11">
|
||||
<list size="14">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
|
@ -35,11 +38,14 @@
|
|||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||
<item index="9" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
||||
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||
<item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
|
||||
<item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
|
||||
<item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
|
|
@ -2,11 +2,7 @@
|
|||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Projects-talk-android.iml" filepath="$PROJECT_DIR$/.idea/modules/Projects-talk-android.iml" group="talk-android" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Projects-talk-android-app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Projects-talk-android-app.iml" group="talk-android/app" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/app.iml" group="talk-android/app" />
|
||||
<module fileurl="file://$PROJECT_DIR$/talk-android.iml" filepath="$PROJECT_DIR$/talk-android.iml" group="talk-android" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/talk-android-app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/talk-android-app.iml" group="talk-android/app" />
|
||||
<module fileurl="file://$PROJECT_DIR$/talk-android.iml" filepath="$PROJECT_DIR$/talk-android.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/talk-android.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/talk-android.app.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -51,6 +51,13 @@
|
|||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.USE_CREDENTIALS"
|
||||
|
@ -87,6 +94,12 @@
|
|||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
@ -100,6 +113,30 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".utils.SyncService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/syncadapter" />
|
||||
<meta-data
|
||||
android:name="android.provider.CONTACTS_STRUCTURE"
|
||||
android:resource="@xml/contacts" />
|
||||
</service>
|
||||
|
||||
<service android:name=".utils.AuthenticatorService">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/auth" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="com.novoda.merlin.MerlinService"
|
||||
android:exported="false" />
|
||||
|
|
|
@ -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<Persistable>
|
||||
@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<RoomOverall> {
|
||||
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<RoomOverall> {
|
||||
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))
|
||||
|
|
|
@ -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<ContactsByNumberOverall> searchContactsByPhoneNumber(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Body RequestBody search);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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<Boolean> screenLockChangeListener;
|
||||
private OnPreferenceValueChangedListener<String> screenLockTimeoutChangeListener;
|
||||
private OnPreferenceValueChangedListener<String> themeChangeListener;
|
||||
private OnPreferenceValueChangedListener<Boolean> 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<String> 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<String> {
|
||||
|
||||
@Override
|
||||
|
@ -797,4 +826,19 @@ public class SettingsController extends BaseController {
|
|||
NextcloudTalkApplication.Companion.setAppTheme(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private class PhoneBookIntegrationChangeListener implements OnPreferenceValueChangedListener<Boolean> {
|
||||
private final Controller controller;
|
||||
|
||||
public PhoneBookIntegrationChangeListener(Controller controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(Boolean newValue) {
|
||||
if (newValue) {
|
||||
ContactAddressBookWorker.Companion.checkPermission(controller, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, Any>()
|
||||
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<ContactsByNumberOverall> {
|
||||
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<String, List<String>> {
|
||||
val result: MutableMap<String, List<String>> = 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<String> = 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<String>()
|
||||
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<ContentProviderOperation>()
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, String> map = new HashMap();
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
43
app/src/main/java/com/nextcloud/talk/utils/SyncAdapter.java
Normal file
43
app/src/main/java/com/nextcloud/talk/utils/SyncAdapter.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
50
app/src/main/java/com/nextcloud/talk/utils/SyncService.java
Normal file
50
app/src/main/java/com/nextcloud/talk/utils/SyncService.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Tobias Kaminsky
|
||||
* Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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<Persistable> 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();
|
||||
|
|
|
@ -239,6 +239,13 @@ public interface AppPreferences {
|
|||
@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);
|
||||
|
||||
|
@ -281,6 +288,20 @@ public interface AppPreferences {
|
|||
@UnregisterChangeListenerMethod
|
||||
void unregisterThemeChangeListener(OnPreferenceValueChangedListener<String> listener);
|
||||
|
||||
@KeyByResource(R.string.nc_settings_phone_book_integration_key)
|
||||
@RegisterChangeListenerMethod
|
||||
void registerPhoneBookIntegrationChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
|
||||
|
||||
@KeyByResource(R.string.nc_settings_phone_book_integration_key)
|
||||
@UnregisterChangeListenerMethod
|
||||
void unregisterPhoneBookIntegrationChangeListener(OnPreferenceValueChangedListener<Boolean> listener);
|
||||
|
||||
@KeyByString("phone_book_integration_last_run")
|
||||
void setPhoneBookIntegrationLastRun(long currentTimeMillis);
|
||||
|
||||
@KeyByString("phone_book_integration_last_run")
|
||||
long getPhoneBookIntegrationLastRun(Long defaultValue);
|
||||
|
||||
@ClearMethod
|
||||
void clear();
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<com.yarolegovich.mp.MaterialSwitchPreference
|
||||
android:id="@+id/settings_phone_book_integration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
apc:mp_default_value="@bool/value_false"
|
||||
apc:mp_key="@string/nc_settings_phone_book_integration_key"
|
||||
apc:mp_summary="@string/nc_settings_phone_book_integration_desc"
|
||||
apc:mp_title="@string/nc_settings_phone_book_integration_title" />
|
||||
</com.yarolegovich.mp.MaterialPreferenceCategory>
|
||||
|
||||
<com.yarolegovich.mp.MaterialPreferenceCategory
|
||||
|
|
|
@ -322,4 +322,8 @@
|
|||
|
||||
<string name="path_password_strike_through" translatable="false"
|
||||
tools:override="true">M3.27,4.27L19.74,20.74</string>
|
||||
<string name="nc_settings_phone_book_integration_key" translatable="false">phone_book_integration</string>
|
||||
<string name="nc_settings_phone_book_integration_desc">Match contacts based on phone number to integrate Talk shortcut in phone book</string>
|
||||
<string name="nc_settings_phone_book_integration_title">Phone book integration</string>
|
||||
<string name="no_phone_book_integration_due_to_permissions">No phone book integration due to missing permissions</string>
|
||||
</resources>
|
||||
|
|
26
app/src/main/res/xml/auth.xml
Normal file
26
app/src/main/res/xml/auth.xml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Tobias Kaminsky
|
||||
~ Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountType="com.nextcloud.talk2"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:smallIcon="@mipmap/ic_launcher"
|
||||
android:label="Nextcloud Talk"/>
|
27
app/src/main/res/xml/contacts.xml
Normal file
27
app/src/main/res/xml/contacts.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Tobias Kaminsky
|
||||
~ Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ContactsDataKind
|
||||
android:mimeType="vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:detailColumn="data2"/>
|
||||
</ContactsAccountType>
|
25
app/src/main/res/xml/syncadapter.xml
Normal file
25
app/src/main/res/xml/syncadapter.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Tobias Kaminsky
|
||||
~ Copyright (C) 2020 Tobias Kaminsky <tobias.kaminsky@nextcloud.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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<sync-adapter
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountType="com.nextcloud.talk2"
|
||||
android:contentAuthority="com.android.contacts" />
|
Loading…
Reference in a new issue