Эх сурвалжийг харах

Beginnings of better notifications

Signed-off-by: Mario Danic <mario@lovelyhq.com>
Mario Danic 6 жил өмнө
parent
commit
60f2e25007
20 өөрчлөгдсөн 1138 нэмэгдсэн , 19 устгасан
  1. 4 3
      app/src/main/java/com/nextcloud/talk/activities/CallActivity.java
  2. 10 3
      app/src/main/java/com/nextcloud/talk/activities/MainActivity.java
  3. 130 0
      app/src/main/java/com/nextcloud/talk/adapters/items/NotificationSoundItem.java
  4. 306 0
      app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java
  5. 10 9
      app/src/main/java/com/nextcloud/talk/controllers/ChatController.java
  6. 283 0
      app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.java
  7. 49 0
      app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java
  8. 2 2
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java
  9. 4 1
      app/src/main/java/com/nextcloud/talk/jobs/NotificationJob.java
  10. 42 0
      app/src/main/java/com/nextcloud/talk/models/RingtoneSettings.java
  11. 37 0
      app/src/main/java/com/nextcloud/talk/models/json/converters/UriTypeConverter.java
  12. 6 1
      app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
  13. 1 0
      app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.java
  14. 21 0
      app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
  15. 25 0
      app/src/main/res/drawable/ic_play_circle_outline_white_24dp.xml
  16. 25 0
      app/src/main/res/drawable/ic_stop_white_24dp.xml
  17. 101 0
      app/src/main/res/layout/controller_call_notification.xml
  18. 24 0
      app/src/main/res/layout/controller_settings.xml
  19. 53 0
      app/src/main/res/layout/rv_item_notification_sound.xml
  20. 5 0
      app/src/main/res/values/strings.xml

+ 4 - 3
app/src/main/java/com/nextcloud/talk/activities/CallActivity.java

@@ -290,7 +290,8 @@ public class CallActivity extends AppCompatActivity {
         callControls.setZ(100.0f);
         basicInitialization();
 
-        if (getIntent().getExtras().containsKey(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
+        if (getIntent().getExtras().containsKey(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL) && TextUtils.isEmpty
+                (roomToken)) {
             handleFromNotification();
         } else {
             initViews();
@@ -878,7 +879,7 @@ public class CallActivity extends AppCompatActivity {
 
     private void joinRoomAndCall() {
         if ("0".equals(callSession)) {
-            ncApi.joinRoom(credentials, ApiUtils.getUrlForRoomParticipants(baseUrl, roomToken), null)
+            ncApi.joinRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(baseUrl, roomToken), null)
                     .subscribeOn(Schedulers.newThread())
                     .observeOn(AndroidSchedulers.mainThread())
                     .retry(3)
@@ -1241,7 +1242,7 @@ public class CallActivity extends AppCompatActivity {
     }
 
     private void leaveRoom() {
-        ncApi.leaveRoom(credentials, ApiUtils.getUrlForRoomParticipants(baseUrl, roomToken))
+        ncApi.leaveRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(baseUrl, roomToken))
                 .subscribeOn(Schedulers.newThread())
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(new Observer<GenericOverall>() {

+ 10 - 3
app/src/main/java/com/nextcloud/talk/activities/MainActivity.java

@@ -36,6 +36,7 @@ import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
+import com.nextcloud.talk.controllers.CallNotificationController;
 import com.nextcloud.talk.controllers.ChatController;
 import com.nextcloud.talk.controllers.MagicBottomNavigationController;
 import com.nextcloud.talk.controllers.ServerSelectionController;
@@ -140,9 +141,15 @@ public final class MainActivity extends AppCompatActivity implements ActionBarPr
         super.onNewIntent(intent);
 
         if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
-            router.pushController(RouterTransaction.with(new ChatController(intent.getExtras()))
-                    .pushChangeHandler(new HorizontalChangeHandler())
-                    .popChangeHandler(new HorizontalChangeHandler()));
+            if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
+                router.pushController(RouterTransaction.with(new CallNotificationController(intent.getExtras()))
+                        .pushChangeHandler(new HorizontalChangeHandler())
+                        .popChangeHandler(new HorizontalChangeHandler()));
+            } else {
+                router.pushController(RouterTransaction.with(new ChatController(intent.getExtras()))
+                        .pushChangeHandler(new HorizontalChangeHandler())
+                        .popChangeHandler(new HorizontalChangeHandler()));
+            }
         }
     }
 

+ 130 - 0
app/src/main/java/com/nextcloud/talk/adapters/items/NotificationSoundItem.java

@@ -0,0 +1,130 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.adapters.items;
+
+import android.view.View;
+import android.widget.TextView;
+
+import com.nextcloud.talk.R;
+import com.nextcloud.talk.utils.MagicFlipView;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
+import eu.davidea.flexibleadapter.items.IFlexible;
+import eu.davidea.viewholders.FlexibleViewHolder;
+
+public class NotificationSoundItem extends AbstractFlexibleItem<NotificationSoundItem.NotificationSoundItemViewHolder> {
+
+    private String notificationSoundName;
+    private String notificationSoundUri;
+
+    private boolean selected;
+
+    private MagicFlipView flipView;
+
+    public NotificationSoundItem(String notificationSoundName, String notificationSoundUri) {
+        this.notificationSoundName = notificationSoundName;
+        this.notificationSoundUri = notificationSoundUri;
+    }
+
+    public String getNotificationSoundUri() {
+        return notificationSoundUri;
+    }
+
+    public String getNotificationSoundName() {
+        return notificationSoundName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return false;
+    }
+
+    @Override
+    public int getLayoutRes() {
+        return R.layout.rv_item_notification_sound;
+    }
+
+    @Override
+    public NotificationSoundItemViewHolder createViewHolder(View view, FlexibleAdapter<IFlexible> adapter) {
+        return new NotificationSoundItemViewHolder(view, adapter);
+    }
+
+    public boolean isSelected() {
+        return selected;
+    }
+
+    public void setSelected(boolean selected) {
+        this.selected = selected;
+    }
+
+    public void flipToFront() {
+        if (flipView != null && flipView.isFlipped()) {
+            flipView.flip(false);
+        }
+    }
+
+    public void flipItemSelection() {
+        if (flipView != null) {
+            flipView.flip(!flipView.isFlipped());
+        }
+    }
+
+    @Override
+    public void bindViewHolder(FlexibleAdapter<IFlexible> adapter, NotificationSoundItemViewHolder holder, int position, List<Object> payloads) {
+        flipView = holder.magicFlipView;
+
+        holder.magicFlipView.flipSilently(adapter.isSelected(position) || isSelected());
+
+        if (isSelected()) {
+            selected = false;
+        }
+
+        holder.notificationName.setText(notificationSoundName);
+
+        if (position == 0) {
+            holder.magicFlipView.setFrontImage(R.drawable.ic_stop_white_24dp);
+        } else {
+            holder.magicFlipView.setFrontImage(R.drawable.ic_play_circle_outline_white_24dp);
+        }
+    }
+
+    static class NotificationSoundItemViewHolder extends FlexibleViewHolder {
+        @BindView(R.id.notificationNameTextView)
+        public TextView notificationName;
+        @BindView(R.id.magicFlipView)
+        MagicFlipView magicFlipView;
+
+        /**
+         * Default constructor.
+         */
+        NotificationSoundItemViewHolder(View view, FlexibleAdapter adapter) {
+            super(view, adapter);
+            ButterKnife.bind(this, view);
+        }
+    }
+
+
+}

+ 306 - 0
app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java

@@ -0,0 +1,306 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.controllers;
+
+import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bluelinelabs.conductor.RouterTransaction;
+import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.load.model.LazyHeaders;
+import com.bumptech.glide.load.resource.bitmap.CircleCrop;
+import com.bumptech.glide.request.RequestOptions;
+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.models.database.UserEntity;
+import com.nextcloud.talk.models.json.participants.Participant;
+import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
+import com.nextcloud.talk.models.json.rooms.Room;
+import com.nextcloud.talk.models.json.rooms.RoomsOverall;
+import com.nextcloud.talk.utils.ApiUtils;
+import com.nextcloud.talk.utils.bundle.BundleKeys;
+import com.nextcloud.talk.utils.glide.GlideApp;
+
+import org.parceler.Parcels;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import autodagger.AutoInjector;
+import butterknife.BindView;
+import butterknife.OnClick;
+import io.reactivex.Observer;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+@AutoInjector(NextcloudTalkApplication.class)
+public class CallNotificationController extends BaseController {
+
+    @Inject
+    NcApi ncApi;
+
+    @BindView(R.id.conversationNameTextView)
+    TextView conversationNameTextView;
+
+    @BindView(R.id.avatarImageView)
+    ImageView avatarImageView;
+    List<Disposable> disposablesList = new ArrayList<>();
+    private Bundle originalBundle;
+    private String roomId;
+    private UserEntity userBeingCalled;
+    private String credentials;
+    private Room currentRoom;
+    private MediaPlayer mediaPlayer;
+    private boolean participantsCheckIsRunning;
+    private boolean leavingScreen = false;
+
+    public CallNotificationController(Bundle args) {
+        super(args);
+        NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
+
+        this.roomId = args.getString(BundleKeys.KEY_ROOM_ID, "");
+        this.userBeingCalled = Parcels.unwrap(args.getParcelable(BundleKeys.KEY_USER_ENTITY));
+
+        this.originalBundle = args;
+
+        credentials = ApiUtils.getCredentials(userBeingCalled.getUserId(), userBeingCalled.getToken());
+    }
+
+    @Override
+    protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
+        return inflater.inflate(R.layout.controller_call_notification, container, false);
+    }
+
+    @OnClick(R.id.callControlHangupView)
+    void hangup() {
+        leavingScreen = true;
+        getRouter().popCurrentController();
+    }
+
+    @OnClick(R.id.callAnswerCameraView)
+    void answerWithCamera() {
+        originalBundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, false);
+        setBackstackAndProceed();
+    }
+
+    @OnClick(R.id.callAnswerVoiceOnlyView)
+    void answerVoiceOnly() {
+        originalBundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true);
+        setBackstackAndProceed();
+    }
+
+    private void setBackstackAndProceed() {
+        originalBundle.putString(BundleKeys.KEY_ROOM_TOKEN, currentRoom.getToken());
+
+        List<RouterTransaction> routerTransactions = new ArrayList<>();
+        routerTransactions.add(RouterTransaction.with(new MagicBottomNavigationController()));
+        routerTransactions.add(RouterTransaction.with(new ChatController(originalBundle)));
+        getRouter().setBackstack(routerTransactions, new HorizontalChangeHandler());
+    }
+
+    private void checkIfAnyParticipantsRemainInRoom() {
+        ncApi.getPeersForCall(credentials, ApiUtils.getUrlForParticipants(userBeingCalled.getBaseUrl(),
+                currentRoom.getToken()))
+                .subscribeOn(Schedulers.newThread())
+                .takeWhile(observable -> !leavingScreen)
+                .retry(3)
+                .subscribe(new Observer<ParticipantsOverall>() {
+                    @Override
+                    public void onSubscribe(Disposable d) {
+                        disposablesList.add(d);
+                        participantsCheckIsRunning = true;
+                    }
+
+                    @Override
+                    public void onNext(ParticipantsOverall participantsOverall) {
+                        boolean hasParticipantsInCall = false;
+                        List<Participant> participantList = participantsOverall.getOcs().getData();
+                        for (Participant participant : participantList) {
+                            if (participant.isInCall()) {
+                                hasParticipantsInCall = true;
+                                break;
+                            }
+                        }
+
+                        if (!hasParticipantsInCall) {
+                            if (getActivity() != null) {
+                                getActivity().runOnUiThread(() -> hangup());
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onError(Throwable e) {
+
+                    }
+
+                    @Override
+                    public void onComplete() {
+                        if (!leavingScreen) {
+                            checkIfAnyParticipantsRemainInRoom();
+                        }
+                    }
+                });
+
+    }
+
+    private void handleFromNotification() {
+        ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(userBeingCalled.getBaseUrl()))
+                .subscribeOn(Schedulers.newThread())
+                .retry(3)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(new Observer<RoomsOverall>() {
+                    @Override
+                    public void onSubscribe(Disposable d) {
+                        disposablesList.add(d);
+                    }
+
+                    @Override
+                    public void onNext(RoomsOverall roomsOverall) {
+                        for (Room room : roomsOverall.getOcs().getData()) {
+                            if (roomId.equals(room.getRoomId())) {
+                                currentRoom = room;
+                                conversationNameTextView.setText(room.getDisplayName());
+                                loadAvatar();
+                                checkIfAnyParticipantsRemainInRoom();
+                                break;
+                            }
+                        }
+
+                    }
+
+                    @Override
+                    public void onError(Throwable e) {
+
+                    }
+
+                    @Override
+                    public void onComplete() {
+
+                    }
+                });
+    }
+
+    @Override
+    protected void onViewBound(@NonNull View view) {
+        super.onViewBound(view);
+
+        getActionBar().hide();
+
+        handleFromNotification();
+
+        Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+
+        mediaPlayer = MediaPlayer.create(getApplicationContext(), ringtoneUri);
+        mediaPlayer.setLooping(true);
+        mediaPlayer.start();
+    }
+
+    private void loadAvatar() {
+        int avatarSize = Math.round(NextcloudTalkApplication
+                .getSharedApplication().getResources().getDimension(R.dimen.avatar_size_big));
+
+        switch (currentRoom.getType()) {
+            case ROOM_TYPE_ONE_TO_ONE_CALL:
+                avatarImageView.setVisibility(View.VISIBLE);
+
+                GlideUrl glideUrl = new GlideUrl(ApiUtils.getUrlForAvatarWithName(userBeingCalled.getBaseUrl(),
+                        currentRoom.getName(), true), new LazyHeaders.Builder()
+                        .setHeader("Accept", "image/*")
+                        .setHeader("User-Agent", ApiUtils.getUserAgent())
+                        .build());
+
+                GlideApp.with(NextcloudTalkApplication.getSharedApplication().getApplicationContext())
+                        .asBitmap()
+                        .diskCacheStrategy(DiskCacheStrategy.NONE)
+                        .load(glideUrl)
+                        .centerInside()
+                        .override(avatarSize, avatarSize)
+                        .apply(RequestOptions.bitmapTransform(new CircleCrop()))
+                        .into(avatarImageView);
+
+                break;
+            case ROOM_GROUP_CALL:
+                GlideApp.with(NextcloudTalkApplication.getSharedApplication().getApplicationContext())
+                        .asBitmap()
+                        .diskCacheStrategy(DiskCacheStrategy.NONE)
+                        .load(R.drawable.ic_group_white_24px)
+                        .centerInside()
+                        .override(avatarSize, avatarSize)
+                        .apply(RequestOptions.bitmapTransform(new CircleCrop()))
+                        .into(avatarImageView);
+            case ROOM_PUBLIC_CALL:
+                GlideApp.with(NextcloudTalkApplication.getSharedApplication().getApplicationContext())
+                        .asBitmap()
+                        .diskCacheStrategy(DiskCacheStrategy.NONE)
+                        .load(R.drawable.ic_link_white_24px)
+                        .centerInside()
+                        .override(avatarSize, avatarSize)
+                        .apply(RequestOptions.bitmapTransform(new CircleCrop()))
+                        .into(avatarImageView);
+                break;
+            default:
+        }
+    }
+
+    private void endMediaPlayer() {
+        if (mediaPlayer != null) {
+            if (mediaPlayer.isPlaying()) {
+                mediaPlayer.stop();
+            }
+
+            mediaPlayer.release();
+            mediaPlayer = null;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        leavingScreen = true;
+        dispose();
+        endMediaPlayer();
+        super.onDestroy();
+    }
+
+    private void dispose() {
+        Disposable disposable;
+        for (int i = 0; i < disposablesList.size(); i++) {
+            if ((disposable = disposablesList.get(i)).isDisposed()) {
+                disposable.dispose();
+            }
+        }
+    }
+}

+ 10 - 9
app/src/main/java/com/nextcloud/talk/controllers/ChatController.java

@@ -156,6 +156,7 @@ public class ChatController extends BaseController implements MessagesListAdapte
     private int newMessagesCount = 0;
     private Boolean startCallFromNotification;
     private String roomId;
+    private boolean voiceOnly = false;
 
     public ChatController(Bundle args) {
         super(args);
@@ -201,6 +202,7 @@ public class ChatController extends BaseController implements MessagesListAdapte
         }
 
         this.startCallFromNotification = args.getBoolean(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL);
+        this.voiceOnly = args.getBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, false);
     }
 
     private void getRoomInfo() {
@@ -250,17 +252,15 @@ public class ChatController extends BaseController implements MessagesListAdapte
                         for (Room room : roomsOverall.getOcs().getData()) {
                             if (roomId.equals(room.getRoomId())) {
                                 roomToken = room.getToken();
+                                conversationName = room.getDisplayName();
+                                setTitle();
                                 break;
                             }
                         }
 
                         if (!TextUtils.isEmpty(roomToken)) {
-                            if (TextUtils.isEmpty(conversationName)) {
-                                getRoomInfo();
-                            } else {
-                                setupMentionAutocomplete();
-                                joinRoomWithPassword();
-                            }
+                            setupMentionAutocomplete();
+                            joinRoomWithPassword();
                         }
                     }
 
@@ -285,6 +285,7 @@ public class ChatController extends BaseController implements MessagesListAdapte
     protected void onViewBound(@NonNull View view) {
         super.onViewBound(view);
 
+        getActionBar().show();
         boolean adapterWasNull = false;
 
         if (adapter == null) {
@@ -476,7 +477,7 @@ public class ChatController extends BaseController implements MessagesListAdapte
     private void joinRoomWithPassword() {
 
         if (currentCall == null) {
-            ncApi.joinRoom(credentials, ApiUtils.getUrlForRoomParticipants(baseUrl, roomToken), roomPassword)
+            ncApi.joinRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(baseUrl, roomToken), roomPassword)
                     .subscribeOn(Schedulers.newThread())
                     .observeOn(AndroidSchedulers.mainThread())
                     .retry(3)
@@ -494,7 +495,7 @@ public class ChatController extends BaseController implements MessagesListAdapte
                             pullChatMessages(0);
                             if (startCallFromNotification != null && startCallFromNotification) {
                                 startCallFromNotification = false;
-                                startACall(false);
+                                startACall(voiceOnly);
                             }
                         }
 
@@ -516,7 +517,7 @@ public class ChatController extends BaseController implements MessagesListAdapte
     }
 
     private void leaveRoom() {
-        ncApi.leaveRoom(credentials, ApiUtils.getUrlForRoomParticipants(baseUrl, roomToken))
+        ncApi.leaveRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(baseUrl, roomToken))
                 .subscribeOn(Schedulers.newThread())
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(new Observer<GenericOverall>() {

+ 283 - 0
app/src/main/java/com/nextcloud/talk/controllers/RingtoneSelectionController.java

@@ -0,0 +1,283 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.controllers;
+
+import android.annotation.SuppressLint;
+import android.database.Cursor;
+import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.bluelinelabs.logansquare.LoganSquare;
+import com.nextcloud.talk.R;
+import com.nextcloud.talk.adapters.items.NotificationSoundItem;
+import com.nextcloud.talk.application.NextcloudTalkApplication;
+import com.nextcloud.talk.controllers.base.BaseController;
+import com.nextcloud.talk.models.RingtoneSettings;
+import com.nextcloud.talk.utils.bundle.BundleKeys;
+import com.nextcloud.talk.utils.preferences.AppPreferences;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import autodagger.AutoInjector;
+import butterknife.BindView;
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.davidea.flexibleadapter.SelectableAdapter;
+import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
+
+@AutoInjector(NextcloudTalkApplication.class)
+public class RingtoneSelectionController extends BaseController implements FlexibleAdapter.OnItemClickListener {
+
+    private static final String TAG = "RingtoneSelectionController";
+
+    @BindView(R.id.recycler_view)
+    RecyclerView recyclerView;
+
+    @BindView(R.id.swipe_refresh_layout)
+    SwipeRefreshLayout swipeRefreshLayout;
+
+    @Inject
+    AppPreferences appPreferences;
+
+    private FlexibleAdapter adapter;
+    private List<AbstractFlexibleItem> abstractFlexibleItemList = new ArrayList<>();
+
+    private boolean callNotificationSounds = false;
+    private MediaPlayer mediaPlayer;
+    private Handler cancelMediaPlayerHandler;
+
+    public RingtoneSelectionController(Bundle args) {
+        super(args);
+
+        this.callNotificationSounds = args.getBoolean(BundleKeys.KEY_ARE_CALL_SOUNDS, false);
+    }
+
+    @Override
+    protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
+        return inflater.inflate(R.layout.controller_generic_rv, container, false);
+    }
+
+    @Override
+    protected void onViewBound(@NonNull View view) {
+        super.onViewBound(view);
+        NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
+
+        setHasOptionsMenu(true);
+
+        if (adapter == null) {
+            adapter = new FlexibleAdapter<>(abstractFlexibleItemList, getActivity(), false);
+
+            adapter.setNotifyChangeOfUnfilteredItems(true)
+                    .setMode(SelectableAdapter.Mode.SINGLE);
+
+            adapter.addListener(this);
+            fetchNotificationSounds();
+
+            cancelMediaPlayerHandler = new Handler();
+        }
+
+        adapter.addListener(this);
+        prepareViews();
+    }
+
+    @Override
+    protected void onAttach(@NonNull View view) {
+        super.onAttach(view);
+
+        if (getActionBar() != null) {
+            getActionBar().setDisplayHomeAsUpEnabled(true);
+        }
+
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                getRouter().popCurrentController();
+                return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    private void prepareViews() {
+        RecyclerView.LayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity());
+        recyclerView.setLayoutManager(layoutManager);
+        recyclerView.setHasFixedSize(true);
+        recyclerView.setAdapter(adapter);
+
+        swipeRefreshLayout.setEnabled(false);
+    }
+
+    @SuppressLint("LongLogTag")
+    private void fetchNotificationSounds() {
+        abstractFlexibleItemList = new ArrayList<>();
+        abstractFlexibleItemList.add(new NotificationSoundItem("None", null));
+
+        int positionToToggle = -1;
+
+        if (getActivity() != null) {
+            RingtoneManager manager = new RingtoneManager(getActivity());
+
+            if (callNotificationSounds) {
+                manager.setType(RingtoneManager.TYPE_RINGTONE);
+            } else {
+                manager.setType(RingtoneManager.TYPE_NOTIFICATION);
+            }
+
+            Cursor cursor = manager.getCursor();
+
+            NotificationSoundItem notificationSoundItem;
+
+            while (cursor.moveToNext()) {
+                String notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
+                String notificationUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX);
+                String completeNotificationUri = notificationUri + "/" + cursor.getString(RingtoneManager
+                        .ID_COLUMN_INDEX);
+
+                notificationSoundItem = new NotificationSoundItem(notificationTitle, completeNotificationUri);
+
+                abstractFlexibleItemList.add(notificationSoundItem);
+
+                String preferencesString;
+                if (callNotificationSounds && !TextUtils.isEmpty(preferencesString = appPreferences
+                        .getCallRingtoneUri()) ||
+                        !callNotificationSounds && !TextUtils.isEmpty(preferencesString = appPreferences
+                                .getMessageRingtoneUri())) {
+                    try {
+                        RingtoneSettings ringtoneSettings = LoganSquare.parse(preferencesString, RingtoneSettings.class);
+                        if (ringtoneSettings.getRingtoneUri() == null) {
+                            ((NotificationSoundItem)abstractFlexibleItemList.get(0)).setSelected(true);
+                        } else if (completeNotificationUri.equals(ringtoneSettings.getRingtoneUri().toString())) {
+                            notificationSoundItem.setSelected(true);
+                        }
+                    } catch (IOException e) {
+                        Log.e(TAG, "Failed to parse ringtone settings");
+                    }
+                }
+
+            }
+
+            cursor.close();
+        }
+
+        adapter.updateDataSet(abstractFlexibleItemList, true);
+    }
+
+    @Override
+    protected String getTitle() {
+        return getResources().getString(R.string.nc_settings_notification_sounds);
+    }
+
+    @SuppressLint("LongLogTag")
+    @Override
+    public boolean onItemClick(View view, int position) {
+        NotificationSoundItem notificationSoundItem = (NotificationSoundItem) adapter.getItem(position);
+
+        Uri ringtoneUri = null;
+        Runnable runnable = () -> endMediaPlayer();
+
+        if (!TextUtils.isEmpty(notificationSoundItem.getNotificationSoundUri())) {
+            ringtoneUri = Uri.parse(notificationSoundItem.getNotificationSoundUri());
+
+            endMediaPlayer();
+            mediaPlayer = MediaPlayer.create(getActivity(), ringtoneUri);
+
+            cancelMediaPlayerHandler = new Handler();
+            cancelMediaPlayerHandler.postDelayed(runnable, mediaPlayer.getDuration() + 25);
+            mediaPlayer.start();
+        }
+
+        RingtoneSettings ringtoneSettings = new RingtoneSettings();
+        ringtoneSettings.setRingtoneName(notificationSoundItem.getNotificationSoundName());
+        ringtoneSettings.setRingtoneUri(ringtoneUri);
+
+        if (callNotificationSounds) {
+            try {
+                appPreferences.setCallRingtoneUri(LoganSquare.serialize(ringtoneSettings));
+                toggleSelection(position);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to store selected ringtone for calls");
+            }
+        } else {
+            try {
+                appPreferences.setMessageRingtoneUri(LoganSquare.serialize(ringtoneSettings));
+                toggleSelection(position);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to store selected ringtone for calls");
+            }
+        }
+
+        return true;
+    }
+
+    private void toggleSelection(int position) {
+        adapter.toggleSelection(position);
+        ((NotificationSoundItem) adapter.getItem(position)).flipItemSelection();
+
+        NotificationSoundItem notificationSoundItem;
+        for (int i = 0; i < adapter.getItemCount(); i++) {
+            if (i != position) {
+                notificationSoundItem = (NotificationSoundItem) adapter.getItem(i);
+                notificationSoundItem.flipToFront();
+            }
+        }
+    }
+
+    private void endMediaPlayer() {
+        if (cancelMediaPlayerHandler != null) {
+            cancelMediaPlayerHandler.removeCallbacksAndMessages(null);
+        }
+
+        if (mediaPlayer != null) {
+            if (mediaPlayer.isPlaying()) {
+                mediaPlayer.stop();
+            }
+
+            mediaPlayer.release();
+            mediaPlayer = null;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        endMediaPlayer();
+        super.onDestroy();
+    }
+
+}

+ 49 - 0
app/src/main/java/com/nextcloud/talk/controllers/SettingsController.java

@@ -24,6 +24,7 @@ import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Bundle;
 import android.security.KeyChain;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -36,7 +37,9 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.bluelinelabs.conductor.RouterTransaction;
+import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
 import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
+import com.bluelinelabs.logansquare.LoganSquare;
 import com.bumptech.glide.load.model.GlideUrl;
 import com.bumptech.glide.load.model.LazyHeaders;
 import com.bumptech.glide.load.resource.bitmap.CircleCrop;
@@ -48,9 +51,11 @@ import com.nextcloud.talk.api.NcApi;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.controllers.base.BaseController;
 import com.nextcloud.talk.jobs.AccountRemovalJob;
+import com.nextcloud.talk.models.RingtoneSettings;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
+import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 import com.nextcloud.talk.utils.glide.GlideApp;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
@@ -65,6 +70,7 @@ import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener;
 
 import org.greenrobot.eventbus.EventBus;
 
+import java.io.IOException;
 import java.net.CookieManager;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -116,6 +122,12 @@ public class SettingsController extends BaseController {
     @BindView(R.id.base_url_text)
     TextView baseUrlTextView;
 
+    @BindView(R.id.settings_call_sound)
+    MaterialStandardPreference settingsCallSounds;
+
+    @BindView(R.id.settings_message_sound)
+    MaterialStandardPreference settingsMessageSound;
+
     @BindView(R.id.settings_remove_account)
     MaterialStandardPreference removeAccountButton;
 
@@ -217,6 +229,21 @@ public class SettingsController extends BaseController {
 
         versionInfo.setSummary("v" + BuildConfig.VERSION_NAME);
 
+        settingsCallSounds.setOnClickListener(v -> {
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(BundleKeys.KEY_ARE_CALL_SOUNDS, true);
+            getRouter().pushController(RouterTransaction.with(new RingtoneSelectionController(bundle))
+                    .pushChangeHandler(new HorizontalChangeHandler()
+                    ).popChangeHandler(new HorizontalChangeHandler()));
+        });
+
+        settingsMessageSound.setOnClickListener(v -> {
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(BundleKeys.KEY_ARE_CALL_SOUNDS, false);
+            getRouter().pushController(RouterTransaction.with(new RingtoneSelectionController(bundle))
+                    .pushChangeHandler(new HorizontalChangeHandler()
+                    ).popChangeHandler(new HorizontalChangeHandler()));
+        });
 
         addAccountButton.addPreferenceClickListener(view15 -> {
             getParentController().getRouter().pushController(RouterTransaction.with(new
@@ -283,6 +310,28 @@ public class SettingsController extends BaseController {
             certificateSetup.setTitle(R.string.nc_client_cert_setup);
         }
 
+        String ringtoneName = "";
+        RingtoneSettings ringtoneSettings;
+        if (!TextUtils.isEmpty(appPreferences.getCallRingtoneUri())) {
+            try {
+                ringtoneSettings = LoganSquare.parse(appPreferences.getCallRingtoneUri(), RingtoneSettings.class);
+                ringtoneName = ringtoneSettings.getRingtoneName();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to parse ringtone name");
+            }
+            settingsCallSounds.setSummary(ringtoneName);
+        }
+
+        if (!TextUtils.isEmpty(appPreferences.getMessageRingtoneUri())) {
+            try {
+                ringtoneSettings = LoganSquare.parse(appPreferences.getMessageRingtoneUri(), RingtoneSettings.class);
+                ringtoneName = ringtoneSettings.getRingtoneName();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to parse ringtone name");
+            }
+            settingsMessageSound.setSummary(ringtoneName);
+        }
+
         if ("No proxy".equals(appPreferences.getProxyType()) || appPreferences.getProxyType() == null) {
             hideProxySettings();
         } else {

+ 2 - 2
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java

@@ -230,7 +230,7 @@ public class OperationsMenuController extends BaseController {
                             .subscribe(operationsObserver);
                     break;
                 case 9:
-                    ncApi.deleteRoom(credentials, ApiUtils.getUrlForRoomParticipants(currentUser.getBaseUrl(), room.getToken()))
+                    ncApi.deleteRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(currentUser.getBaseUrl(), room.getToken()))
                             .subscribeOn(Schedulers.newThread())
                             .observeOn(AndroidSchedulers.mainThread())
                             .retry(1)
@@ -317,7 +317,7 @@ public class OperationsMenuController extends BaseController {
 
                     break;
                 case 99:
-                    ncApi.joinRoom(credentials, ApiUtils.getUrlForRoomParticipants(baseUrl, conversationToken),
+                    ncApi.joinRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(baseUrl, conversationToken),
                             callPassword)
                             .subscribeOn(Schedulers.newThread())
                             .observeOn(AndroidSchedulers.mainThread())

+ 4 - 1
app/src/main/java/com/nextcloud/talk/jobs/NotificationJob.java

@@ -129,6 +129,7 @@ public class NotificationJob extends Job {
                                 intent = new Intent(context, CallActivity.class);
                             }
 
+                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                             bundle.putString(BundleKeys.KEY_ROOM_ID, decryptedPushMessage.getId());
                             bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap(signatureVerification
                                     .getUserEntity()));
@@ -177,6 +178,7 @@ public class NotificationJob extends Job {
                                     .setSubText(signatureVerification.getUserEntity().getDisplayName())
                                     .setContentTitle(decryptedPushMessage.getSubject())
                                     .setSound(soundUri)
+                                    .setFullScreenIntent(pendingIntent, true)
                                     .setAutoCancel(true);
 
                             if (Build.VERSION.SDK_INT >= 23) {
@@ -221,7 +223,8 @@ public class NotificationJob extends Job {
                                 notificationBuilder.setGroup(Long.toString(crc32.getValue()));
                             }
 
-                            notificationBuilder.setContentIntent(pendingIntent);
+                            //notificationBuilder.setContentIntent(pendingIntent);
+                            notificationBuilder.setFullScreenIntent(pendingIntent, true);
 
                             String stringForCrc = decryptedPushMessage.getSubject() + " " + signatureVerification
                                     .getUserEntity().getDisplayName() + " " + signatureVerification.getUserEntity

+ 42 - 0
app/src/main/java/com/nextcloud/talk/models/RingtoneSettings.java

@@ -0,0 +1,42 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.models;
+
+import android.net.Uri;
+import android.support.annotation.Nullable;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+import com.nextcloud.talk.models.json.converters.UriTypeConverter;
+
+import org.parceler.Parcel;
+
+import lombok.Data;
+
+@Parcel
+@JsonObject
+@Data
+public class RingtoneSettings {
+    @JsonField(name = "ringtoneUri", typeConverter = UriTypeConverter.class)
+    @Nullable Uri ringtoneUri;
+    @JsonField(name = "ringtoneName")
+    String ringtoneName;
+}

+ 37 - 0
app/src/main/java/com/nextcloud/talk/models/json/converters/UriTypeConverter.java

@@ -0,0 +1,37 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.models.json.converters;
+
+import android.net.Uri;
+
+import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter;
+
+public class UriTypeConverter extends StringBasedTypeConverter<Uri> {
+    @Override
+    public Uri getFromString(String string) {
+        return Uri.parse(string);
+    }
+
+    @Override
+    public String convertToString(Uri object) {
+        return object.toString();
+    }
+}

+ 6 - 1
app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java

@@ -60,10 +60,15 @@ public class ApiUtils {
         return retrofitBucket;
     }
 
-    public static String getUrlForRoomParticipants(String baseUrl, String token) {
+    public static String getUrlForSettingMyselfAsActiveParticipant(String baseUrl, String token) {
         return getRoom(baseUrl, token) + "/participants/active";
     }
 
+
+    public static String getUrlForParticipants(String baseUrl, String token) {
+        return getRoom(baseUrl, token) + "/participants";
+    }
+
     public static String getUrlForCapabilities(String baseUrl) {
         return baseUrl + ocsApiVersion + "/cloud/capabilities";
     }

+ 1 - 0
app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.java

@@ -50,4 +50,5 @@ public class BundleKeys {
     public static final String KEY_SPREED_CAPABILITIES = "KEY_SPREED_CAPABILITIES";
     public static final String KEY_FROM_NOTIFICATION_START_CALL = "KEY_FROM_NOTIFICATION_START_CALL";
     public static final String KEY_ROOM_ID = "KEY_ROOM_ID";
+    public static final String KEY_ARE_CALL_SOUNDS = "KEY_ARE_CALL_SOUNDS";
 }

+ 21 - 0
app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java

@@ -138,6 +138,27 @@ public interface AppPreferences {
     @RemoveMethod
     void removePushToTalkIntroShown();
 
+    @KeyByString("call_ringtone")
+    String getCallRingtoneUri();
+
+    @KeyByString("call_ringtone")
+    void setCallRingtoneUri(String value);
+
+    @KeyByString("call_ringtone")
+    @RemoveMethod
+    void removeCallRingtoneUri();
+
+    @KeyByString("message_ringtone")
+    String getMessageRingtoneUri();
+
+    @KeyByString("message_ringtone")
+    void setMessageRingtoneUri(String value);
+
+    @KeyByString("message_ringtone")
+    @RemoveMethod
+    void removeMessageRingtoneUri();
+
+
     @ClearMethod
     void clear();
 }

+ 25 - 0
app/src/main/res/drawable/ic_play_circle_outline_white_24dp.xml

@@ -0,0 +1,25 @@
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Mario Danic
+  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<vector android:autoMirrored="true" android:height="24dp"
+    android:tint="#FFFFFF" android:viewportHeight="24.0"
+    android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>

+ 25 - 0
app/src/main/res/drawable/ic_stop_white_24dp.xml

@@ -0,0 +1,25 @@
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Mario Danic
+  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<vector android:autoMirrored="true" android:height="24dp"
+    android:tint="#FFFFFF" android:viewportHeight="24.0"
+    android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M6,6h12v12H6z"/>
+</vector>

+ 101 - 0
app/src/main/res/layout/controller_call_notification.xml

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Mario Danic
+  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@color/white">
+
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:layout_alignParentTop="true"
+        android:text="@string/nc_incoming_call"
+        android:textAlignment="center"
+        android:textColor="@color/colorPrimary"
+        android:textSize="20sp"
+        android:layout_marginTop="48dp"
+        android:id="@+id/incomingCallTextView"/>
+
+    <ImageView
+        android:id="@+id/avatarImageView"
+        android:layout_width="@dimen/avatar_size_big"
+        android:layout_height="@dimen/avatar_size_big"
+        android:layout_centerHorizontal="true"
+        android:layout_below="@id/incomingCallTextView"
+        android:layout_margin="16dp"/>
+
+    <TextView
+        android:id="@+id/conversationNameTextView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/avatarImageView"
+        android:layout_centerInParent="true"
+        android:textAlignment="center"
+        android:textColor="@color/colorPrimary"
+        android:textSize="16sp"
+        />
+
+
+    <com.nextcloud.talk.utils.MagicFlipView
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/callAnswerVoiceOnlyView"
+        android:layout_width="60dp"
+        android:layout_height="60dp"
+        android:layout_above="@id/callControlHangupView"
+        android:layout_centerHorizontal="true"
+        android:layout_toStartOf="@id/callControlHangupView"
+        app:checked="false"
+        app:enableInitialAnimation="false"
+        app:frontBackgroundColor="@color/colorPrimary"
+        app:frontImage="@drawable/ic_mic_white_24px"/>
+
+    <com.nextcloud.talk.utils.MagicFlipView
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/callAnswerCameraView"
+        android:layout_width="60dp"
+        android:layout_height="60dp"
+        android:layout_above="@id/callControlHangupView"
+        android:layout_toEndOf="@id/callControlHangupView"
+        app:checked="false"
+        app:enableInitialAnimation="false"
+        app:frontBackgroundColor="@color/colorPrimary"
+        app:frontImage="@drawable/ic_videocam_white_24px"/>
+
+
+    <com.nextcloud.talk.utils.MagicFlipView
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/callControlHangupView"
+        android:layout_width="60dp"
+        android:layout_height="60dp"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="24dp"
+        android:layout_marginBottom="64dp"
+        app:checked="false"
+        app:enableInitialAnimation="false"
+        app:frontBackgroundColor="@color/nc_darkRed"
+        app:frontImage="@drawable/ic_call_end_white_24px"/>
+
+
+</RelativeLayout>

+ 24 - 0
app/src/main/res/layout/controller_settings.xml

@@ -111,6 +111,30 @@
 
     </com.yarolegovich.mp.MaterialPreferenceCategory>
 
+    <com.yarolegovich.mp.MaterialPreferenceCategory
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:animateLayoutChanges="true"
+        apc:mpc_title="@string/nc_settings_notification_sounds"
+        apc:mpc_title_color="@color/colorPrimary">
+
+        <com.yarolegovich.mp.MaterialStandardPreference
+            android:id="@+id/settings_call_sound"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            apc:mp_key="@string/nc_settings_call_ringtone_key"
+            apc:mp_title="@string/nc_settings_call_ringtone"/>
+
+        <com.yarolegovich.mp.MaterialStandardPreference
+            android:id="@+id/settings_message_sound"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            apc:mp_key="@string/nc_settings_message_ringtone_key"
+            apc:mp_title="@string/nc_settings_message_ringtone"/>
+
+    </com.yarolegovich.mp.MaterialPreferenceCategory>
+
+
     <com.yarolegovich.mp.MaterialPreferenceCategory
         android:layout_width="match_parent"
         android:layout_height="wrap_content"

+ 53 - 0
app/src/main/res/layout/rv_item_notification_sound.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Mario Danic
+  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/item_height"
+                android:orientation="vertical">
+
+
+    <com.nextcloud.talk.utils.MagicFlipView
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/magicFlipView"
+        android:layout_width="@dimen/avatar_size"
+        android:layout_height="@dimen/avatar_size"
+        android:layout_centerVertical="true"
+        android:layout_marginEnd="@dimen/activity_horizontal_margin"
+        android:layout_marginStart="24dp"
+        app:animationDuration="170"
+        app:checked="false"
+        app:enableInitialAnimation="true"
+        app:frontBackgroundColor="@color/colorPrimary"
+        app:frontImage="@drawable/ic_play_circle_outline_white_24dp"
+        app:rearBackgroundColor="@color/colorPrimary"/>
+
+    <TextView
+        android:id="@+id/notificationNameTextView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_margin="8dp"
+        android:layout_toEndOf="@id/magicFlipView"
+        android:ellipsize="end"
+        android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+</RelativeLayout>

+ 5 - 0
app/src/main/res/values/strings.xml

@@ -60,6 +60,11 @@
     <string name="nc_settings_no_talk_installed">Talk app is not installed on the server you tried to authorize against</string>
     <string name="nc_settings_account_updated">Your already existing account was updated, instead of adding a new one</string>
     <string name="nc_account_scheduled_for_deletion">The account is scheduled for deletion, and cannot be changed</string>
+    <string name="nc_settings_notification_sounds">Notification sounds</string>
+    <string name="nc_settings_call_ringtone">Calls</string>
+    <string name="nc_settings_call_ringtone_key">call_ringtone</string>
+    <string name="nc_settings_message_ringtone">Messages</string>
+    <string name="nc_settings_message_ringtone_key">message_ringtone</string>
 
     <string name="nc_no_proxy">No proxy</string>
     <string name="nc_username">Username</string>