mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 20:19:42 +01:00
Merge pull request #1794 from nextcloud/feature/1761/selectAudioOutput
Feature/1761/select audio output
This commit is contained in:
commit
f076a81427
@ -82,6 +82,7 @@ import com.nextcloud.talk.models.json.signaling.Signaling;
|
|||||||
import com.nextcloud.talk.models.json.signaling.SignalingOverall;
|
import com.nextcloud.talk.models.json.signaling.SignalingOverall;
|
||||||
import com.nextcloud.talk.models.json.signaling.settings.IceServer;
|
import com.nextcloud.talk.models.json.signaling.settings.IceServer;
|
||||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
|
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
|
||||||
|
import com.nextcloud.talk.ui.dialog.AudioOutputDialog;
|
||||||
import com.nextcloud.talk.utils.ApiUtils;
|
import com.nextcloud.talk.utils.ApiUtils;
|
||||||
import com.nextcloud.talk.utils.DisplayUtils;
|
import com.nextcloud.talk.utils.DisplayUtils;
|
||||||
import com.nextcloud.talk.utils.NotificationUtils;
|
import com.nextcloud.talk.utils.NotificationUtils;
|
||||||
@ -142,6 +143,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
import autodagger.AutoInjector;
|
import autodagger.AutoInjector;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Observer;
|
import io.reactivex.Observer;
|
||||||
@ -170,6 +173,8 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
public static final String TAG = "CallActivity";
|
public static final String TAG = "CallActivity";
|
||||||
|
|
||||||
|
public MagicAudioManager audioManager;
|
||||||
|
|
||||||
private static final String[] PERMISSIONS_CALL = {
|
private static final String[] PERMISSIONS_CALL = {
|
||||||
android.Manifest.permission.CAMERA,
|
android.Manifest.permission.CAMERA,
|
||||||
android.Manifest.permission.RECORD_AUDIO,
|
android.Manifest.permission.RECORD_AUDIO,
|
||||||
@ -195,7 +200,6 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
private MediaConstraints videoConstraints;
|
private MediaConstraints videoConstraints;
|
||||||
private MediaConstraints sdpConstraints;
|
private MediaConstraints sdpConstraints;
|
||||||
private MediaConstraints sdpConstraintsForMCU;
|
private MediaConstraints sdpConstraintsForMCU;
|
||||||
private MagicAudioManager audioManager;
|
|
||||||
private VideoSource videoSource;
|
private VideoSource videoSource;
|
||||||
private VideoTrack localVideoTrack;
|
private VideoTrack localVideoTrack;
|
||||||
private AudioSource audioSource;
|
private AudioSource audioSource;
|
||||||
@ -252,6 +256,8 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
private CallActivityBinding binding;
|
private CallActivityBinding binding;
|
||||||
|
|
||||||
|
private AudioOutputDialog audioOutputDialog;
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public enum CallStatus {
|
public enum CallStatus {
|
||||||
CONNECTING, CALLING_TIMEOUT, JOINED, IN_CONVERSATION, RECONNECTING, OFFLINE, LEAVING, PUBLISHER_FAILED
|
CONNECTING, CALLING_TIMEOUT, JOINED, IN_CONVERSATION, RECONNECTING, OFFLINE, LEAVING, PUBLISHER_FAILED
|
||||||
@ -327,15 +333,9 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
private void initClickListeners() {
|
private void initClickListeners() {
|
||||||
binding.pictureInPictureButton.setOnClickListener(l -> enterPipMode());
|
binding.pictureInPictureButton.setOnClickListener(l -> enterPipMode());
|
||||||
|
|
||||||
binding.speakerButton.setOnClickListener(l -> {
|
binding.audioOutputButton.setOnClickListener(v -> {
|
||||||
if (audioManager != null) {
|
audioOutputDialog = new AudioOutputDialog(this);
|
||||||
audioManager.toggleUseSpeakerphone();
|
audioOutputDialog.show();
|
||||||
if (audioManager.isSpeakerphoneAutoOn()) {
|
|
||||||
binding.speakerButton.getHierarchy().setPlaceholderImage(R.drawable.ic_volume_up_white_24dp);
|
|
||||||
} else {
|
|
||||||
binding.speakerButton.getHierarchy().setPlaceholderImage(R.drawable.ic_volume_mute_white_24dp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
binding.microphoneButton.setOnClickListener(l -> onMicrophoneClick());
|
binding.microphoneButton.setOnClickListener(l -> onMicrophoneClick());
|
||||||
@ -377,8 +377,8 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
boolean camera2EnumeratorIsSupported = false;
|
boolean camera2EnumeratorIsSupported = false;
|
||||||
try {
|
try {
|
||||||
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(this);
|
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(this);
|
||||||
} catch (final Throwable throwable) {
|
} catch (final Throwable t) {
|
||||||
Log.w(TAG, "Camera2Enumator threw an error");
|
Log.w(TAG, "Camera2Enumerator threw an error", t);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (camera2EnumeratorIsSupported) {
|
if (camera2EnumeratorIsSupported) {
|
||||||
@ -412,12 +412,18 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
|
|
||||||
// Create and audio manager that will take care of audio routing,
|
// Create and audio manager that will take care of audio routing,
|
||||||
// audio modes, audio device enumeration etc.
|
// audio modes, audio device enumeration etc.
|
||||||
audioManager = MagicAudioManager.create(getApplicationContext(), !isVoiceOnlyCall);
|
audioManager = MagicAudioManager.create(getApplicationContext(), isVoiceOnlyCall);
|
||||||
// Store existing audio settings and change audio mode to
|
// Store existing audio settings and change audio mode to
|
||||||
// MODE_IN_COMMUNICATION for best possible VoIP performance.
|
// MODE_IN_COMMUNICATION for best possible VoIP performance.
|
||||||
Log.d(TAG, "Starting the audio manager...");
|
Log.d(TAG, "Starting the audio manager...");
|
||||||
audioManager.start(this::onAudioManagerDevicesChanged);
|
audioManager.start(this::onAudioManagerDevicesChanged);
|
||||||
|
|
||||||
|
if (isVoiceOnlyCall) {
|
||||||
|
setAudioOutputChannel(MagicAudioManager.AudioDevice.EARPIECE);
|
||||||
|
} else {
|
||||||
|
setAudioOutputChannel(MagicAudioManager.AudioDevice.SPEAKER_PHONE);
|
||||||
|
}
|
||||||
|
|
||||||
iceServers = new ArrayList<>();
|
iceServers = new ArrayList<>();
|
||||||
|
|
||||||
//create sdpConstraints
|
//create sdpConstraints
|
||||||
@ -448,6 +454,38 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
microphoneInitialization();
|
microphoneInitialization();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAudioOutputChannel(MagicAudioManager.AudioDevice selectedAudioDevice) {
|
||||||
|
if (audioManager != null) {
|
||||||
|
audioManager.selectAudioDevice(selectedAudioDevice);
|
||||||
|
updateAudioOutputButton(audioManager.getCurrentAudioDevice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAudioOutputButton(MagicAudioManager.AudioDevice activeAudioDevice) {
|
||||||
|
switch (activeAudioDevice) {
|
||||||
|
case BLUETOOTH:
|
||||||
|
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
|
||||||
|
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_bluetooth_audio_24));
|
||||||
|
break;
|
||||||
|
case SPEAKER_PHONE:
|
||||||
|
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
|
||||||
|
AppCompatResources.getDrawable(context, R.drawable.ic_volume_up_white_24dp));
|
||||||
|
break;
|
||||||
|
case EARPIECE:
|
||||||
|
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
|
||||||
|
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_phone_in_talk_24));
|
||||||
|
break;
|
||||||
|
case WIRED_HEADSET:
|
||||||
|
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
|
||||||
|
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_headset_mic_24));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.e(TAG, "Icon for audio output not available");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DrawableCompat.setTint(binding.audioOutputButton.getDrawable(), Color.WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleFromNotification() {
|
private void handleFromNotification() {
|
||||||
int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
|
int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
|
||||||
|
|
||||||
@ -496,7 +534,6 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isVoiceOnlyCall) {
|
if (isVoiceOnlyCall) {
|
||||||
binding.speakerButton.setVisibility(View.VISIBLE);
|
|
||||||
binding.switchSelfVideoButton.setVisibility(View.GONE);
|
binding.switchSelfVideoButton.setVisibility(View.GONE);
|
||||||
binding.cameraButton.setVisibility(View.GONE);
|
binding.cameraButton.setVisibility(View.GONE);
|
||||||
binding.selfVideoRenderer.setVisibility(View.GONE);
|
binding.selfVideoRenderer.setVisibility(View.GONE);
|
||||||
@ -513,7 +550,6 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
params.setMargins(0, 0, 0, 0);
|
params.setMargins(0, 0, 0, 0);
|
||||||
binding.gridview.setLayoutParams(params);
|
binding.gridview.setLayoutParams(params);
|
||||||
|
|
||||||
binding.speakerButton.setVisibility(View.GONE);
|
|
||||||
if (cameraEnumerator.getDeviceNames().length < 2) {
|
if (cameraEnumerator.getDeviceNames().length < 2) {
|
||||||
binding.switchSelfVideoButton.setVisibility(View.GONE);
|
binding.switchSelfVideoButton.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@ -713,19 +749,25 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onAudioManagerDevicesChanged(
|
private void onAudioManagerDevicesChanged(
|
||||||
final MagicAudioManager.AudioDevice device, final Set<MagicAudioManager.AudioDevice> availableDevices) {
|
final MagicAudioManager.AudioDevice currentDevice,
|
||||||
|
final Set<MagicAudioManager.AudioDevice> availableDevices) {
|
||||||
Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
|
Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
|
||||||
+ "selected: " + device);
|
+ "currentDevice: " + currentDevice);
|
||||||
|
|
||||||
final boolean shouldDisableProximityLock = (device.equals(MagicAudioManager.AudioDevice.WIRED_HEADSET)
|
final boolean shouldDisableProximityLock = (currentDevice.equals(MagicAudioManager.AudioDevice.WIRED_HEADSET)
|
||||||
|| device.equals(MagicAudioManager.AudioDevice.SPEAKER_PHONE)
|
|| currentDevice.equals(MagicAudioManager.AudioDevice.SPEAKER_PHONE)
|
||||||
|| device.equals(MagicAudioManager.AudioDevice.BLUETOOTH));
|
|| currentDevice.equals(MagicAudioManager.AudioDevice.BLUETOOTH));
|
||||||
|
|
||||||
if (shouldDisableProximityLock) {
|
if (shouldDisableProximityLock) {
|
||||||
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK);
|
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK);
|
||||||
} else {
|
} else {
|
||||||
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITH_PROXIMITY_SENSOR_LOCK);
|
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITH_PROXIMITY_SENSOR_LOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audioOutputDialog != null) {
|
||||||
|
audioOutputDialog.updateOutputDeviceList();
|
||||||
|
}
|
||||||
|
updateAudioOutputButton(currentDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1641,10 +1683,10 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
Log.d(TAG, " currentSessionId is " + currentSessionId);
|
Log.d(TAG, " currentSessionId is " + currentSessionId);
|
||||||
|
|
||||||
for (HashMap<String, Object> participant : users) {
|
for (HashMap<String, Object> participant : users) {
|
||||||
long inCallFlag = (long)participant.get("inCall");
|
long inCallFlag = (long) participant.get("inCall");
|
||||||
if (!participant.get("sessionId").equals(currentSessionId)) {
|
if (!participant.get("sessionId").equals(currentSessionId)) {
|
||||||
boolean isNewSession;
|
boolean isNewSession;
|
||||||
Log.d(TAG, " inCallFlag of participant " + participant.get("sessionId").toString().substring(0,4) + " : " + inCallFlag);
|
Log.d(TAG, " inCallFlag of participant " + participant.get("sessionId").toString().substring(0, 4) + " : " + inCallFlag);
|
||||||
isNewSession = inCallFlag != 0;
|
isNewSession = inCallFlag != 0;
|
||||||
|
|
||||||
if (isNewSession) {
|
if (isNewSession) {
|
||||||
@ -1654,7 +1696,7 @@ public class CallActivity extends CallBaseActivity {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, " inCallFlag of currentSessionId: " + inCallFlag);
|
Log.d(TAG, " inCallFlag of currentSessionId: " + inCallFlag);
|
||||||
if (inCallFlag == 0){
|
if (inCallFlag == 0) {
|
||||||
Log.d(TAG, "Most probably a moderator ended the call for all.");
|
Log.d(TAG, "Most probably a moderator ended the call for all.");
|
||||||
hangup(true);
|
hangup(true);
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ public class MenuItem extends AbstractFlexibleItem<MenuItem.MenuItemViewHolder>
|
|||||||
this.title = title;
|
this.title = title;
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
padding = (int) DisplayUtils.convertDpToPixel(16,
|
padding = (int) DisplayUtils.convertDpToPixel(32,
|
||||||
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext());
|
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
*
|
||||||
|
* 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.ui.dialog
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.activities.CallActivity
|
||||||
|
import com.nextcloud.talk.databinding.DialogAudioOutputBinding
|
||||||
|
import com.nextcloud.talk.webrtc.MagicAudioManager
|
||||||
|
|
||||||
|
class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(callActivity) {
|
||||||
|
|
||||||
|
private lateinit var dialogAudioOutputBinding: DialogAudioOutputBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
dialogAudioOutputBinding = DialogAudioOutputBinding.inflate(layoutInflater)
|
||||||
|
setContentView(dialogAudioOutputBinding.root)
|
||||||
|
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
|
||||||
|
updateOutputDeviceList()
|
||||||
|
initClickListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateOutputDeviceList() {
|
||||||
|
if (callActivity.audioManager?.audioDevices?.contains(MagicAudioManager.AudioDevice.BLUETOOTH) == false) {
|
||||||
|
dialogAudioOutputBinding.audioOutputBluetooth.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
dialogAudioOutputBinding.audioOutputBluetooth.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callActivity.audioManager?.audioDevices?.contains(MagicAudioManager.AudioDevice.EARPIECE) == false) {
|
||||||
|
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callActivity.audioManager?.audioDevices?.contains(MagicAudioManager.AudioDevice.SPEAKER_PHONE) == false) {
|
||||||
|
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callActivity.audioManager?.currentAudioDevice?.equals(
|
||||||
|
MagicAudioManager.AudioDevice.WIRED_HEADSET
|
||||||
|
) == true
|
||||||
|
) {
|
||||||
|
dialogAudioOutputBinding.audioOutputEarspeaker.visibility = View.GONE
|
||||||
|
dialogAudioOutputBinding.audioOutputSpeaker.visibility = View.GONE
|
||||||
|
dialogAudioOutputBinding.audioOutputWiredHeadset.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
dialogAudioOutputBinding.audioOutputWiredHeadset.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightActiveOutputChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun highlightActiveOutputChannel() {
|
||||||
|
when (callActivity.audioManager?.currentAudioDevice) {
|
||||||
|
MagicAudioManager.AudioDevice.BLUETOOTH -> {
|
||||||
|
dialogAudioOutputBinding.audioOutputBluetoothIcon.setColorFilter(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
context,
|
||||||
|
R.color.colorPrimary
|
||||||
|
),
|
||||||
|
android.graphics.PorterDuff.Mode.SRC_IN
|
||||||
|
)
|
||||||
|
dialogAudioOutputBinding.audioOutputBluetoothText.setTextColor(
|
||||||
|
callActivity.resources.getColor(R.color.colorPrimary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
MagicAudioManager.AudioDevice.SPEAKER_PHONE -> {
|
||||||
|
dialogAudioOutputBinding.audioOutputSpeakerIcon.setColorFilter(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
context,
|
||||||
|
R.color.colorPrimary
|
||||||
|
),
|
||||||
|
android.graphics.PorterDuff.Mode.SRC_IN
|
||||||
|
)
|
||||||
|
dialogAudioOutputBinding.audioOutputSpeakerText.setTextColor(
|
||||||
|
callActivity.resources.getColor(R.color.colorPrimary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
MagicAudioManager.AudioDevice.EARPIECE -> {
|
||||||
|
dialogAudioOutputBinding.audioOutputEarspeakerIcon.setColorFilter(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
context,
|
||||||
|
R.color.colorPrimary
|
||||||
|
),
|
||||||
|
android.graphics.PorterDuff.Mode.SRC_IN
|
||||||
|
)
|
||||||
|
dialogAudioOutputBinding.audioOutputEarspeakerText.setTextColor(
|
||||||
|
callActivity.resources.getColor(R.color.colorPrimary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
MagicAudioManager.AudioDevice.WIRED_HEADSET -> {
|
||||||
|
dialogAudioOutputBinding.audioOutputWiredHeadsetIcon.setColorFilter(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
context,
|
||||||
|
R.color.colorPrimary
|
||||||
|
),
|
||||||
|
android.graphics.PorterDuff.Mode.SRC_IN
|
||||||
|
)
|
||||||
|
dialogAudioOutputBinding.audioOutputWiredHeadsetText.setTextColor(
|
||||||
|
callActivity.resources.getColor(R.color.colorPrimary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Log.d(TAG, "AudioOutputDialog doesn't know this AudioDevice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initClickListeners() {
|
||||||
|
dialogAudioOutputBinding.audioOutputBluetooth.setOnClickListener {
|
||||||
|
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.BLUETOOTH)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogAudioOutputBinding.audioOutputSpeaker.setOnClickListener {
|
||||||
|
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.SPEAKER_PHONE)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogAudioOutputBinding.audioOutputEarspeaker.setOnClickListener {
|
||||||
|
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.EARPIECE)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
val bottomSheet = findViewById<View>(R.id.design_bottom_sheet)
|
||||||
|
val behavior = BottomSheetBehavior.from(bottomSheet as View)
|
||||||
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AudioOutputDialog"
|
||||||
|
}
|
||||||
|
}
|
@ -41,8 +41,10 @@ import android.media.AudioDeviceInfo;
|
|||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.nextcloud.talk.events.PeerConnectionEvent;
|
import com.nextcloud.talk.events.PeerConnectionEvent;
|
||||||
import com.nextcloud.talk.utils.power.PowerManagerUtils;
|
import com.nextcloud.talk.utils.power.PowerManagerUtils;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.webrtc.ThreadUtils;
|
import org.webrtc.ThreadUtils;
|
||||||
|
|
||||||
@ -55,45 +57,25 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
public class MagicAudioManager {
|
public class MagicAudioManager {
|
||||||
private static final String TAG = "MagicAudioManager";
|
private static final String TAG = "MagicAudioManager";
|
||||||
private static final String SPEAKERPHONE_AUTO = "auto";
|
|
||||||
private static final String SPEAKERPHONE_FALSE = "false";
|
|
||||||
private final Context magicContext;
|
private final Context magicContext;
|
||||||
// Handles all tasks related to Bluetooth headset devices.
|
|
||||||
private final MagicBluetoothManager bluetoothManager;
|
private final MagicBluetoothManager bluetoothManager;
|
||||||
// Contains speakerphone setting: auto, true or false
|
private boolean useProximitySensor;
|
||||||
private String useSpeakerphone;
|
|
||||||
private AudioManager audioManager;
|
private AudioManager audioManager;
|
||||||
private AudioManagerEvents audioManagerEvents;
|
private AudioManagerListener audioManagerListener;
|
||||||
private AudioManagerState amState;
|
private AudioManagerState amState;
|
||||||
private int savedAudioMode = AudioManager.MODE_INVALID;
|
private int savedAudioMode = AudioManager.MODE_INVALID;
|
||||||
private boolean savedIsSpeakerPhoneOn = false;
|
private boolean savedIsSpeakerPhoneOn = false;
|
||||||
private boolean savedIsMicrophoneMute = false;
|
private boolean savedIsMicrophoneMute = false;
|
||||||
private boolean hasWiredHeadset = false;
|
private boolean hasWiredHeadset = false;
|
||||||
// Default audio device; speaker phone for video calls or earpiece for audio
|
|
||||||
// only calls.
|
|
||||||
private AudioDevice defaultAudioDevice;
|
|
||||||
// Contains the currently selected audio device.
|
|
||||||
// This device is changed automatically using a certain scheme where e.g.
|
|
||||||
// a wired headset "wins" over speaker phone. It is also possible for a
|
|
||||||
// user to explicitly select a device (and overrid any predefined scheme).
|
|
||||||
// See |userSelectedAudioDevice| for details.
|
|
||||||
private AudioDevice selectedAudioDevice;
|
|
||||||
// Contains the user-selected audio device which overrides the predefined
|
|
||||||
// selection scheme.
|
|
||||||
// TODO(henrika): always set to AudioDevice.NONE today. Add support for
|
|
||||||
// explicit selection based on choice by userSelectedAudioDevice.
|
|
||||||
private AudioDevice userSelectedAudioDevice;
|
private AudioDevice userSelectedAudioDevice;
|
||||||
// Proximity sensor object. It measures the proximity of an object in cm
|
private AudioDevice currentAudioDevice;
|
||||||
// relative to the view screen of a device and can therefore be used to
|
|
||||||
// assist device switching (close to ear <=> use headset earpiece if
|
|
||||||
// available, far from ear <=> use speaker phone).
|
|
||||||
private MagicProximitySensor proximitySensor = null;
|
private MagicProximitySensor proximitySensor = null;
|
||||||
// Contains a list of available audio devices. A Set collection is used to
|
|
||||||
// avoid duplicate elements.
|
|
||||||
private Set<AudioDevice> audioDevices = new HashSet<>();
|
private Set<AudioDevice> audioDevices = new HashSet<>();
|
||||||
// Broadcast receiver for wired headset intent broadcasts.
|
|
||||||
private BroadcastReceiver wiredHeadsetReceiver;
|
private BroadcastReceiver wiredHeadsetReceiver;
|
||||||
// Callback method for changes in audio focus.
|
|
||||||
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
|
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
|
||||||
|
|
||||||
private PowerManagerUtils powerManagerUtils;
|
private PowerManagerUtils powerManagerUtils;
|
||||||
@ -110,18 +92,8 @@ public class MagicAudioManager {
|
|||||||
powerManagerUtils = new PowerManagerUtils();
|
powerManagerUtils = new PowerManagerUtils();
|
||||||
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITH_PROXIMITY_SENSOR_LOCK);
|
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITH_PROXIMITY_SENSOR_LOCK);
|
||||||
|
|
||||||
if (useProximitySensor) {
|
this.useProximitySensor = useProximitySensor;
|
||||||
useSpeakerphone = SPEAKERPHONE_AUTO;
|
updateAudioDeviceState();
|
||||||
} else {
|
|
||||||
useSpeakerphone = SPEAKERPHONE_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) {
|
|
||||||
defaultAudioDevice = AudioDevice.EARPIECE;
|
|
||||||
} else {
|
|
||||||
defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and initialize the proximity sensor.
|
// Create and initialize the proximity sensor.
|
||||||
// Tablet devices (e.g. Nexus 7) does not support proximity sensors.
|
// Tablet devices (e.g. Nexus 7) does not support proximity sensors.
|
||||||
@ -134,8 +106,6 @@ public class MagicAudioManager {
|
|||||||
onProximitySensorChangedState();
|
onProximitySensorChangedState();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,48 +115,29 @@ public class MagicAudioManager {
|
|||||||
return new MagicAudioManager(context, useProximitySensor);
|
return new MagicAudioManager(context, useProximitySensor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleUseSpeakerphone() {
|
|
||||||
if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) {
|
|
||||||
useSpeakerphone = SPEAKERPHONE_AUTO;
|
|
||||||
setDefaultAudioDevice(AudioDevice.SPEAKER_PHONE);
|
|
||||||
} else {
|
|
||||||
useSpeakerphone = SPEAKERPHONE_FALSE;
|
|
||||||
setDefaultAudioDevice(AudioDevice.EARPIECE);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAudioDeviceState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSpeakerphoneAutoOn() {
|
|
||||||
return (useSpeakerphone.equals(SPEAKERPHONE_AUTO));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called when the proximity sensor reports a state change,
|
* This method is called when the proximity sensor reports a state change, e.g. from "NEAR to FAR" or from "FAR to
|
||||||
* e.g. from "NEAR to FAR" or from "FAR to NEAR".
|
* NEAR".
|
||||||
*/
|
*/
|
||||||
private void onProximitySensorChangedState() {
|
private void onProximitySensorChangedState() {
|
||||||
|
if (!useProximitySensor) {
|
||||||
if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The proximity sensor should only be activated when there are exactly two
|
if (userSelectedAudioDevice.equals(AudioDevice.SPEAKER_PHONE)
|
||||||
// available audio devices.
|
&& audioDevices.contains(AudioDevice.EARPIECE)
|
||||||
if (audioDevices.size() == 2 && audioDevices.contains(MagicAudioManager.AudioDevice.EARPIECE)
|
&& audioDevices.contains(AudioDevice.SPEAKER_PHONE)) {
|
||||||
&& audioDevices.contains(MagicAudioManager.AudioDevice.SPEAKER_PHONE)) {
|
|
||||||
if (proximitySensor.sensorReportsNearState()) {
|
if (proximitySensor.sensorReportsNearState()) {
|
||||||
// Sensor reports that a "handset is being held up to a person's ear",
|
setAudioDeviceInternal(AudioDevice.EARPIECE);
|
||||||
// or "something is covering the light sensor".
|
Log.d(TAG, "switched to EARPIECE because userSelectedAudioDevice was SPEAKER_PHONE and proximity=near");
|
||||||
setAudioDeviceInternal(MagicAudioManager.AudioDevice.EARPIECE);
|
|
||||||
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
||||||
.SENSOR_NEAR, null, null, null, null));
|
.SENSOR_NEAR, null, null, null, null));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Sensor reports that a "handset is removed from a person's ear", or
|
|
||||||
// "the light sensor is no longer covered".
|
|
||||||
setAudioDeviceInternal(MagicAudioManager.AudioDevice.SPEAKER_PHONE);
|
setAudioDeviceInternal(MagicAudioManager.AudioDevice.SPEAKER_PHONE);
|
||||||
|
Log.d(TAG, "switched to SPEAKER_PHONE because userSelectedAudioDevice was SPEAKER_PHONE and proximity=far");
|
||||||
|
|
||||||
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
|
||||||
.SENSOR_FAR, null, null, null, null));
|
.SENSOR_FAR, null, null, null, null));
|
||||||
@ -195,7 +146,7 @@ public class MagicAudioManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
public void start(AudioManagerEvents audioManagerEvents) {
|
public void start(AudioManagerListener audioManagerListener) {
|
||||||
Log.d(TAG, "start");
|
Log.d(TAG, "start");
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
if (amState == AudioManagerState.RUNNING) {
|
if (amState == AudioManagerState.RUNNING) {
|
||||||
@ -205,7 +156,7 @@ public class MagicAudioManager {
|
|||||||
// TODO(henrika): perhaps call new method called preInitAudio() here if UNINITIALIZED.
|
// TODO(henrika): perhaps call new method called preInitAudio() here if UNINITIALIZED.
|
||||||
|
|
||||||
Log.d(TAG, "AudioManager starts...");
|
Log.d(TAG, "AudioManager starts...");
|
||||||
this.audioManagerEvents = audioManagerEvents;
|
this.audioManagerListener = audioManagerListener;
|
||||||
amState = AudioManagerState.RUNNING;
|
amState = AudioManagerState.RUNNING;
|
||||||
|
|
||||||
// Store current audio state so we can restore it when stop() is called.
|
// Store current audio state so we can restore it when stop() is called.
|
||||||
@ -274,7 +225,7 @@ public class MagicAudioManager {
|
|||||||
|
|
||||||
// Set initial device states.
|
// Set initial device states.
|
||||||
userSelectedAudioDevice = AudioDevice.NONE;
|
userSelectedAudioDevice = AudioDevice.NONE;
|
||||||
selectedAudioDevice = AudioDevice.NONE;
|
currentAudioDevice = AudioDevice.NONE;
|
||||||
audioDevices.clear();
|
audioDevices.clear();
|
||||||
|
|
||||||
// Initialize and start Bluetooth if a BT device is available or initiate
|
// Initialize and start Bluetooth if a BT device is available or initiate
|
||||||
@ -324,7 +275,7 @@ public class MagicAudioManager {
|
|||||||
|
|
||||||
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.IDLE);
|
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.IDLE);
|
||||||
|
|
||||||
audioManagerEvents = null;
|
audioManagerListener = null;
|
||||||
Log.d(TAG, "AudioManager stopped");
|
Log.d(TAG, "AudioManager stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,21 +284,16 @@ public class MagicAudioManager {
|
|||||||
/**
|
/**
|
||||||
* Changes selection of the currently active audio device.
|
* Changes selection of the currently active audio device.
|
||||||
*/
|
*/
|
||||||
private void setAudioDeviceInternal(AudioDevice device) {
|
private void setAudioDeviceInternal(AudioDevice audioDevice) {
|
||||||
Log.d(TAG, "setAudioDeviceInternal(device=" + device + ")");
|
Log.d(TAG, "setAudioDeviceInternal(device=" + audioDevice + ")");
|
||||||
|
|
||||||
if (audioDevices.contains(device)) {
|
if (audioDevices.contains(audioDevice)) {
|
||||||
|
switch (audioDevice) {
|
||||||
switch (device) {
|
|
||||||
case SPEAKER_PHONE:
|
case SPEAKER_PHONE:
|
||||||
setSpeakerphoneOn(true);
|
setSpeakerphoneOn(true);
|
||||||
break;
|
break;
|
||||||
case EARPIECE:
|
case EARPIECE:
|
||||||
setSpeakerphoneOn(false);
|
|
||||||
break;
|
|
||||||
case WIRED_HEADSET:
|
case WIRED_HEADSET:
|
||||||
setSpeakerphoneOn(false);
|
|
||||||
break;
|
|
||||||
case BLUETOOTH:
|
case BLUETOOTH:
|
||||||
setSpeakerphoneOn(false);
|
setSpeakerphoneOn(false);
|
||||||
break;
|
break;
|
||||||
@ -355,35 +301,10 @@ public class MagicAudioManager {
|
|||||||
Log.e(TAG, "Invalid audio device selection");
|
Log.e(TAG, "Invalid audio device selection");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
selectedAudioDevice = device;
|
currentAudioDevice = audioDevice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes default audio device.
|
|
||||||
* TODO(henrika): add usage of this method in the AppRTCMobile client.
|
|
||||||
*/
|
|
||||||
public void setDefaultAudioDevice(AudioDevice defaultDevice) {
|
|
||||||
ThreadUtils.checkIsOnMainThread();
|
|
||||||
switch (defaultDevice) {
|
|
||||||
case SPEAKER_PHONE:
|
|
||||||
defaultAudioDevice = defaultDevice;
|
|
||||||
break;
|
|
||||||
case EARPIECE:
|
|
||||||
if (hasEarpiece()) {
|
|
||||||
defaultAudioDevice = defaultDevice;
|
|
||||||
} else {
|
|
||||||
defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.e(TAG, "Invalid default audio device selection");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
|
|
||||||
updateAudioDeviceState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes selection of the currently active audio device.
|
* Changes selection of the currently active audio device.
|
||||||
*/
|
*/
|
||||||
@ -407,9 +328,9 @@ public class MagicAudioManager {
|
|||||||
/**
|
/**
|
||||||
* Returns the currently selected audio device.
|
* Returns the currently selected audio device.
|
||||||
*/
|
*/
|
||||||
public AudioDevice getSelectedAudioDevice() {
|
public AudioDevice getCurrentAudioDevice() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
return selectedAudioDevice;
|
return currentAudioDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -456,11 +377,9 @@ public class MagicAudioManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a wired headset is connected or not.
|
* Checks whether a wired headset is connected or not. This is not a valid indication that audio playback is
|
||||||
* This is not a valid indication that audio playback is actually over
|
* actually over the wired headset as audio routing depends on other conditions. We only use it as an early
|
||||||
* the wired headset as audio routing depends on other conditions. We
|
* indicator (during initialization) of an attached wired headset.
|
||||||
* only use it as an early indicator (during initialization) of an attached
|
|
||||||
* wired headset.
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
private boolean hasWiredHeadset() {
|
private boolean hasWiredHeadset() {
|
||||||
@ -482,10 +401,6 @@ public class MagicAudioManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates list of possible audio devices and make new device selection.
|
|
||||||
* TODO(henrika): add unit test to verify all state transitions.
|
|
||||||
*/
|
|
||||||
public void updateAudioDeviceState() {
|
public void updateAudioDeviceState() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
Log.d(TAG, "--- updateAudioDeviceState: "
|
Log.d(TAG, "--- updateAudioDeviceState: "
|
||||||
@ -493,19 +408,15 @@ public class MagicAudioManager {
|
|||||||
+ "BT state=" + bluetoothManager.getState());
|
+ "BT state=" + bluetoothManager.getState());
|
||||||
Log.d(TAG, "Device status: "
|
Log.d(TAG, "Device status: "
|
||||||
+ "available=" + audioDevices + ", "
|
+ "available=" + audioDevices + ", "
|
||||||
+ "selected=" + selectedAudioDevice + ", "
|
+ "current=" + currentAudioDevice + ", "
|
||||||
+ "user selected=" + userSelectedAudioDevice);
|
+ "user selected=" + userSelectedAudioDevice);
|
||||||
|
|
||||||
// Check if any Bluetooth headset is connected. The internal BT state will
|
|
||||||
// change accordingly.
|
|
||||||
// TODO(henrika): perhaps wrap required state into BT manager.
|
|
||||||
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
|
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
|
|| bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_DISCONNECTING) {
|
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_DISCONNECTING) {
|
||||||
bluetoothManager.updateDevice();
|
bluetoothManager.updateDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the set of available audio devices.
|
|
||||||
Set<AudioDevice> newAudioDevices = new HashSet<>();
|
Set<AudioDevice> newAudioDevices = new HashSet<>();
|
||||||
|
|
||||||
if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED
|
if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED
|
||||||
@ -518,33 +429,28 @@ public class MagicAudioManager {
|
|||||||
// If a wired headset is connected, then it is the only possible option.
|
// If a wired headset is connected, then it is the only possible option.
|
||||||
newAudioDevices.add(AudioDevice.WIRED_HEADSET);
|
newAudioDevices.add(AudioDevice.WIRED_HEADSET);
|
||||||
} else {
|
} else {
|
||||||
// No wired headset, hence the audio-device list can contain speaker
|
|
||||||
// phone (on a tablet), or speaker phone and earpiece (on mobile phone).
|
|
||||||
newAudioDevices.add(AudioDevice.SPEAKER_PHONE);
|
newAudioDevices.add(AudioDevice.SPEAKER_PHONE);
|
||||||
if (hasEarpiece()) {
|
if (hasEarpiece()) {
|
||||||
newAudioDevices.add(AudioDevice.EARPIECE);
|
newAudioDevices.add(AudioDevice.EARPIECE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Store state which is set to true if the device list has changed.
|
|
||||||
boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices);
|
boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices);
|
||||||
// Update the existing audio device set.
|
|
||||||
audioDevices = newAudioDevices;
|
audioDevices = newAudioDevices;
|
||||||
|
|
||||||
|
|
||||||
// Correct user selected audio devices if needed.
|
// Correct user selected audio devices if needed.
|
||||||
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
|
if (userSelectedAudioDevice == AudioDevice.BLUETOOTH
|
||||||
&& userSelectedAudioDevice == AudioDevice.BLUETOOTH) {
|
&& bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE) {
|
||||||
// If BT is not available, it can't be the user selection.
|
|
||||||
userSelectedAudioDevice = AudioDevice.NONE;
|
|
||||||
}
|
|
||||||
if (hasWiredHeadset && userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE) {
|
|
||||||
// If user selected speaker phone, but then plugged wired headset then make
|
|
||||||
// wired headset as user selected device.
|
|
||||||
userSelectedAudioDevice = AudioDevice.WIRED_HEADSET;
|
|
||||||
}
|
|
||||||
if (!hasWiredHeadset && userSelectedAudioDevice == AudioDevice.WIRED_HEADSET) {
|
|
||||||
// If user selected wired headset, but then unplugged wired headset then make
|
|
||||||
// speaker phone as user selected device.
|
|
||||||
userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
|
userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
|
||||||
}
|
}
|
||||||
|
if (userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE && hasWiredHeadset) {
|
||||||
|
userSelectedAudioDevice = AudioDevice.WIRED_HEADSET;
|
||||||
|
}
|
||||||
|
if (userSelectedAudioDevice == AudioDevice.WIRED_HEADSET && !hasWiredHeadset) {
|
||||||
|
userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Need to start Bluetooth if it is available and user either selected it explicitly or
|
// Need to start Bluetooth if it is available and user either selected it explicitly or
|
||||||
// user did not select any output device.
|
// user did not select any output device.
|
||||||
@ -586,42 +492,41 @@ public class MagicAudioManager {
|
|||||||
|
|
||||||
|
|
||||||
// Update selected audio device.
|
// Update selected audio device.
|
||||||
AudioDevice newAudioDevice = selectedAudioDevice;
|
AudioDevice newCurrentAudioDevice;
|
||||||
|
|
||||||
if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
|
if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
|
||||||
// If a Bluetooth is connected, then it should be used as output audio
|
// If a Bluetooth is connected, then it should be used as output audio
|
||||||
// device. Note that it is not sufficient that a headset is available;
|
// device. Note that it is not sufficient that a headset is available;
|
||||||
// an active SCO channel must also be up and running.
|
// an active SCO channel must also be up and running.
|
||||||
newAudioDevice = AudioDevice.BLUETOOTH;
|
newCurrentAudioDevice = AudioDevice.BLUETOOTH;
|
||||||
} else if (hasWiredHeadset) {
|
} else if (hasWiredHeadset) {
|
||||||
// If a wired headset is connected, but Bluetooth is not, then wired headset is used as
|
// If a wired headset is connected, but Bluetooth is not, then wired headset is used as
|
||||||
// audio device.
|
// audio device.
|
||||||
newAudioDevice = AudioDevice.WIRED_HEADSET;
|
newCurrentAudioDevice = AudioDevice.WIRED_HEADSET;
|
||||||
} else {
|
} else {
|
||||||
// No wired headset and no Bluetooth, hence the audio-device list can contain speaker
|
// No wired headset and no Bluetooth, hence the audio-device list can contain speaker
|
||||||
// phone (on a tablet), or speaker phone and earpiece (on mobile phone).
|
// phone (on a tablet), or speaker phone and earpiece (on mobile phone).
|
||||||
// |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or AudioDevice.EARPIECE
|
// |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or AudioDevice.EARPIECE
|
||||||
// depending on the user's selection.
|
// depending on the user's selection.
|
||||||
newAudioDevice = defaultAudioDevice;
|
newCurrentAudioDevice = userSelectedAudioDevice;
|
||||||
}
|
}
|
||||||
// Switch to new device but only if there has been any changes.
|
// Switch to new device but only if there has been any changes.
|
||||||
if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) {
|
if (newCurrentAudioDevice != currentAudioDevice || audioDeviceSetUpdated) {
|
||||||
// Do the required device switch.
|
// Do the required device switch.
|
||||||
setAudioDeviceInternal(newAudioDevice);
|
setAudioDeviceInternal(newCurrentAudioDevice);
|
||||||
Log.d(TAG, "New device status: "
|
Log.d(TAG, "New device status: "
|
||||||
+ "available=" + audioDevices + ", "
|
+ "available=" + audioDevices + ", "
|
||||||
+ "selected=" + newAudioDevice);
|
+ "current(new)=" + newCurrentAudioDevice);
|
||||||
if (audioManagerEvents != null) {
|
if (audioManagerListener != null) {
|
||||||
// Notify a listening client that audio device has been changed.
|
// Notify a listening client that audio device has been changed.
|
||||||
audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices);
|
audioManagerListener.onAudioDeviceChanged(currentAudioDevice, audioDevices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.d(TAG, "--- updateAudioDeviceState done");
|
Log.d(TAG, "--- updateAudioDeviceState done");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AudioDevice is the names of possible audio devices that we currently
|
* AudioDevice is the names of possible audio devices that we currently support.
|
||||||
* support.
|
|
||||||
*/
|
*/
|
||||||
public enum AudioDevice {
|
public enum AudioDevice {
|
||||||
SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, NONE
|
SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, NONE
|
||||||
@ -639,7 +544,7 @@ public class MagicAudioManager {
|
|||||||
/**
|
/**
|
||||||
* Selected audio device change event.
|
* Selected audio device change event.
|
||||||
*/
|
*/
|
||||||
public static interface AudioManagerEvents {
|
public static interface AudioManagerListener {
|
||||||
// Callback fired once audio device is changed or list of available audio devices changed.
|
// Callback fired once audio device is changed or list of available audio devices changed.
|
||||||
void onAudioDeviceChanged(
|
void onAudioDeviceChanged(
|
||||||
AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices);
|
AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices);
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
<!--
|
|
||||||
@author Google LLC
|
|
||||||
Copyright (C) 2021 Google LLC
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M2,12.5C2,9.46 4.46,7 7.5,7H18c2.21,0 4,1.79 4,4s-1.79,4 -4,4H9.5C8.12,15 7,13.88 7,12.5S8.12,10 9.5,10H17v2H9.41c-0.55,0 -0.55,1 0,1H18c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2H7.5C5.57,9 4,10.57 4,12.5S5.57,16 7.5,16H17v2H7.5C4.46,18 2,15.54 2,12.5z"/>
|
|
||||||
</vector>
|
|
10
app/src/main/res/drawable/ic_baseline_bluetooth_audio_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_bluetooth_audio_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 -0.16,-1.59 -0.43,-2.31l-2.33,2.32zM19.53,6.71l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zM15.71,7.71L10,2L9,2v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41L9,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59L11,5.83zM12.88,16.29L11,18.17v-3.76l1.88,1.88z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_baseline_headset_mic_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_headset_mic_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9 -9,-9z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_baseline_phone_in_talk_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_phone_in_talk_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM19,12h2c0,-4.97 -4.03,-9 -9,-9v2c3.87,0 7,3.13 7,7zM15,12h2c0,-2.76 -2.24,-5 -5,-5v2c1.66,0 3,1.34 3,3z"/>
|
||||||
|
</vector>
|
@ -140,59 +140,66 @@
|
|||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:weightSum="5">
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/pictureInPictureButton"
|
android:id="@+id/pictureInPictureButton"
|
||||||
android:layout_width="60dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:elevation="10dp"
|
android:elevation="10dp"
|
||||||
app:backgroundImage="@color/call_buttons_background"
|
app:backgroundImage="@color/call_buttons_background"
|
||||||
app:placeholderImage="@drawable/ic_baseline_picture_in_picture_alt_24"
|
app:placeholderImage="@drawable/ic_baseline_picture_in_picture_alt_24"
|
||||||
app:roundAsCircle="true" />
|
app:roundAsCircle="true"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/speakerButton"
|
android:id="@+id/audioOutputButton"
|
||||||
android:layout_width="60dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
app:backgroundImage="@color/call_buttons_background"
|
app:backgroundImage="@color/call_buttons_background"
|
||||||
app:placeholderImage="@drawable/ic_volume_mute_white_24dp"
|
app:placeholderImage="@drawable/ic_volume_mute_white_24dp"
|
||||||
app:roundAsCircle="true" />
|
app:roundAsCircle="true"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/cameraButton"
|
android:id="@+id/cameraButton"
|
||||||
android:layout_width="60dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:alpha="0.7"
|
android:alpha="0.7"
|
||||||
app:backgroundImage="@color/call_buttons_background"
|
app:backgroundImage="@color/call_buttons_background"
|
||||||
app:placeholderImage="@drawable/ic_videocam_white_24px"
|
app:placeholderImage="@drawable/ic_videocam_white_24px"
|
||||||
app:roundAsCircle="true" />
|
app:roundAsCircle="true"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/microphoneButton"
|
android:id="@+id/microphoneButton"
|
||||||
android:layout_width="60dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:alpha="0.7"
|
android:alpha="0.7"
|
||||||
app:backgroundImage="@color/call_buttons_background"
|
app:backgroundImage="@color/call_buttons_background"
|
||||||
app:placeholderImage="@drawable/ic_mic_off_white_24px"
|
app:placeholderImage="@drawable/ic_mic_off_white_24px"
|
||||||
app:roundAsCircle="true" />
|
app:roundAsCircle="true"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/hangupButton"
|
android:id="@+id/hangupButton"
|
||||||
android:layout_width="60dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="20dp"
|
||||||
app:backgroundImage="@color/nc_darkRed"
|
app:backgroundImage="@color/nc_darkRed"
|
||||||
app:placeholderImage="@drawable/ic_call_end_white_24px"
|
app:placeholderImage="@drawable/ic_call_end_white_24px"
|
||||||
app:roundAsCircle="true" />
|
app:roundAsCircle="true"
|
||||||
|
android:layout_weight="1"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -27,13 +27,15 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_bottom_sheet"
|
android:background="@color/bg_bottom_sheet"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="@dimen/standard_padding">
|
android:paddingStart="@dimen/standard_padding"
|
||||||
|
android:paddingEnd="@dimen/standard_padding"
|
||||||
|
android:paddingBottom="@dimen/standard_half_padding">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/upload"
|
android:id="@+id/upload"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
android:padding="@dimen/standard_padding"
|
android:gravity="start|center_vertical"
|
||||||
android:text="@string/nc_add_file"
|
android:text="@string/nc_add_file"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="@color/medium_emphasis_text"
|
android:textColor="@color/medium_emphasis_text"
|
||||||
@ -42,13 +44,10 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_attach_contact"
|
android:id="@+id/menu_attach_contact"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingLeft="@dimen/standard_padding"
|
|
||||||
android:paddingTop="@dimen/standard_half_padding"
|
|
||||||
android:paddingRight="@dimen/standard_padding"
|
|
||||||
android:paddingBottom="@dimen/standard_half_padding"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@ -64,7 +63,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:layout_gravity="start|center_vertical"
|
||||||
android:layout_marginStart="@dimen/standard_margin"
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
android:text="@string/nc_share_contact"
|
android:text="@string/nc_share_contact"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="@color/high_emphasis_text"
|
android:textColor="@color/high_emphasis_text"
|
||||||
@ -75,13 +75,10 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_share_location"
|
android:id="@+id/menu_share_location"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingLeft="@dimen/standard_padding"
|
|
||||||
android:paddingTop="@dimen/standard_half_padding"
|
|
||||||
android:paddingRight="@dimen/standard_padding"
|
|
||||||
android:paddingBottom="@dimen/standard_half_padding"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@ -97,7 +94,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:layout_gravity="start|center_vertical"
|
||||||
android:layout_marginStart="@dimen/standard_margin"
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
android:text="@string/nc_share_location"
|
android:text="@string/nc_share_location"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="@color/high_emphasis_text"
|
android:textColor="@color/high_emphasis_text"
|
||||||
@ -108,13 +106,10 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_attach_picture_from_cam"
|
android:id="@+id/menu_attach_picture_from_cam"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingLeft="@dimen/standard_padding"
|
|
||||||
android:paddingTop="@dimen/standard_half_padding"
|
|
||||||
android:paddingRight="@dimen/standard_padding"
|
|
||||||
android:paddingBottom="@dimen/standard_half_padding"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@ -130,7 +125,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:layout_gravity="start|center_vertical"
|
||||||
android:layout_marginStart="@dimen/standard_margin"
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
android:text="@string/nc_upload_picture_from_cam"
|
android:text="@string/nc_upload_picture_from_cam"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="@color/high_emphasis_text"
|
android:textColor="@color/high_emphasis_text"
|
||||||
@ -141,13 +137,10 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_attach_file_from_local"
|
android:id="@+id/menu_attach_file_from_local"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingLeft="@dimen/standard_padding"
|
|
||||||
android:paddingTop="@dimen/standard_half_padding"
|
|
||||||
android:paddingRight="@dimen/standard_padding"
|
|
||||||
android:paddingBottom="@dimen/standard_half_padding"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@ -163,7 +156,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:layout_gravity="start|center_vertical"
|
||||||
android:layout_marginStart="@dimen/standard_margin"
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
android:text="@string/nc_upload_local_file"
|
android:text="@string/nc_upload_local_file"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="@color/high_emphasis_text"
|
android:textColor="@color/high_emphasis_text"
|
||||||
@ -174,13 +168,10 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_attach_file_from_cloud"
|
android:id="@+id/menu_attach_file_from_cloud"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingLeft="@dimen/standard_padding"
|
|
||||||
android:paddingTop="@dimen/standard_half_padding"
|
|
||||||
android:paddingRight="@dimen/standard_padding"
|
|
||||||
android:paddingBottom="@dimen/standard_padding"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@ -196,7 +187,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:layout_gravity="start|center_vertical"
|
||||||
android:layout_marginStart="@dimen/standard_margin"
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="@color/high_emphasis_text"
|
android:textColor="@color/high_emphasis_text"
|
||||||
android:textSize="@dimen/bottom_sheet_text_size"
|
android:textSize="@dimen/bottom_sheet_text_size"
|
||||||
|
165
app/src/main/res/layout/dialog_audio_output.xml
Normal file
165
app/src/main/res/layout/dialog_audio_output.xml
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 Marcel Hibbe <marcel.hibbe@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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/bg_call_screen_dialog"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/standard_padding"
|
||||||
|
android:paddingEnd="@dimen/standard_padding"
|
||||||
|
android:paddingBottom="@dimen/standard_half_padding">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/upload"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:text="@string/audio_output_dialog_headline"
|
||||||
|
android:textColor="@color/medium_emphasis_text_dark_background"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/audio_output_bluetooth"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/audio_output_bluetooth_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_baseline_bluetooth_audio_24"
|
||||||
|
app:tint="@color/grey_600" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/audio_output_bluetooth_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
|
android:text="@string/audio_output_bluetooth"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/high_emphasis_text_dark_background"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/audio_output_speaker"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/audio_output_speaker_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_volume_up_white_24dp"
|
||||||
|
app:tint="@color/grey_600" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/audio_output_speaker_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
|
android:text="@string/audio_output_speaker"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/high_emphasis_text_dark_background"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/audio_output_earspeaker"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/audio_output_earspeaker_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_baseline_phone_in_talk_24"
|
||||||
|
app:tint="@color/grey_600" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/audio_output_earspeaker_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
|
android:text="@string/audio_output_phone"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/high_emphasis_text_dark_background"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/audio_output_wired_headset"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/audio_output_wired_headset_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_baseline_headset_mic_24"
|
||||||
|
app:tint="@color/grey_600" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/audio_output_wired_headset_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
|
android:text="@string/audio_output_wired_headset"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/high_emphasis_text_dark_background"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -2,6 +2,8 @@
|
|||||||
~ Nextcloud Talk application
|
~ Nextcloud Talk application
|
||||||
~
|
~
|
||||||
~ @author Mario Danic
|
~ @author Mario Danic
|
||||||
|
~ @author Andy Scherzinger
|
||||||
|
~ Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||||
~
|
~
|
||||||
~ This program is free software: you can redistribute it and/or modify
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
@ -18,27 +20,36 @@
|
|||||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
style="@style/MD_ListItem">
|
style="@style/MD_ListItem">
|
||||||
<RelativeLayout
|
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="56dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/icon"
|
android:id="@+id/icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scaleType="center"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
tools:src="@drawable/ic_delete_grey600_24dp"
|
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
android:scaleType="center"
|
||||||
|
app:tint="@color/grey_600"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
/>
|
tools:src="@drawable/ic_delete_grey600_24dp" />
|
||||||
|
|
||||||
<com.afollestad.materialdialogs.internal.rtl.RtlTextView
|
<com.afollestad.materialdialogs.internal.rtl.RtlTextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
tools:text="Item"
|
android:layout_width="match_parent"
|
||||||
android:layout_toEndOf="@id/icon"
|
android:layout_height="wrap_content"
|
||||||
style="@style/MD_ListItemText" />
|
android:layout_gravity="start|center_vertical"
|
||||||
</RelativeLayout>
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/standard_padding"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/high_emphasis_text"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size"
|
||||||
|
tools:text="Menu item" />
|
||||||
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -24,7 +24,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_default">
|
android:background="@color/bg_default"
|
||||||
|
android:minHeight="@dimen/bottom_sheet_item_height">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/menu_text"
|
android:id="@+id/menu_text"
|
||||||
@ -35,9 +36,9 @@
|
|||||||
android:focusableInTouchMode="false"
|
android:focusableInTouchMode="false"
|
||||||
android:gravity="start|center_vertical"
|
android:gravity="start|center_vertical"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="@color/conversation_item_header"
|
android:textColor="@color/high_emphasis_text"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
tools:drawablePadding="16dp"
|
tools:drawablePadding="32dp"
|
||||||
tools:drawableStart="@drawable/ic_add_grey600_24px"
|
tools:drawableStart="@drawable/ic_add_grey600_24px"
|
||||||
tools:text="Start a new conversation" />
|
tools:text="Start a new conversation" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -40,6 +40,10 @@
|
|||||||
<color name="medium_emphasis_text">#99000000</color>
|
<color name="medium_emphasis_text">#99000000</color>
|
||||||
<color name="low_emphasis_text">#61000000</color>
|
<color name="low_emphasis_text">#61000000</color>
|
||||||
|
|
||||||
|
<!-- general text colors for dark background -->
|
||||||
|
<color name="high_emphasis_text_dark_background">#deffffff</color>
|
||||||
|
<color name="medium_emphasis_text_dark_background">#99ffffff</color>
|
||||||
|
|
||||||
<!-- Text color of sent messages -->
|
<!-- Text color of sent messages -->
|
||||||
<color name="nc_outcoming_text_default">#FFFFFF</color>
|
<color name="nc_outcoming_text_default">#FFFFFF</color>
|
||||||
<!-- Text color of received messages -->
|
<!-- Text color of received messages -->
|
||||||
@ -78,6 +82,8 @@
|
|||||||
<color name="bg_message_list_outcoming_bubble_deleted">#800082C9</color>
|
<color name="bg_message_list_outcoming_bubble_deleted">#800082C9</color>
|
||||||
|
|
||||||
<color name="bg_bottom_sheet">#46ffffff</color>
|
<color name="bg_bottom_sheet">#46ffffff</color>
|
||||||
|
<color name="bg_call_screen_dialog">#121212</color>
|
||||||
|
<color name="call_screen_text">#ffffffff</color>
|
||||||
|
|
||||||
<color name="call_buttons_background">#BF999999</color>
|
<color name="call_buttons_background">#BF999999</color>
|
||||||
<color name="favorite_icon_tint">#FFCC00</color>
|
<color name="favorite_icon_tint">#FFCC00</color>
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
|
||||||
<dimen name="item_height">72dp</dimen>
|
<dimen name="item_height">72dp</dimen>
|
||||||
|
<dimen name="bottom_sheet_item_height">56dp</dimen>
|
||||||
<dimen name="small_item_height">48dp</dimen>
|
<dimen name="small_item_height">48dp</dimen>
|
||||||
|
|
||||||
<dimen name="min_size_clickable_area">48dp</dimen>
|
<dimen name="min_size_clickable_area">48dp</dimen>
|
||||||
|
@ -494,5 +494,10 @@
|
|||||||
<string name="take_photo_send">Send</string>
|
<string name="take_photo_send">Send</string>
|
||||||
<string name="take_photo_error_deleting_picture">Error taking picture</string>
|
<string name="take_photo_error_deleting_picture">Error taking picture</string>
|
||||||
<string name="take_photo_permission">Taking a photo is not possible without permissions</string>
|
<string name="take_photo_permission">Taking a photo is not possible without permissions</string>
|
||||||
|
<string name="audio_output_bluetooth">Bluetooth</string>
|
||||||
|
<string name="audio_output_speaker">Speaker</string>
|
||||||
|
<string name="audio_output_phone">Phone</string>
|
||||||
|
<string name="audio_output_dialog_headline">Audio output</string>
|
||||||
|
<string name="audio_output_wired_headset">Wired headset</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -39,7 +39,7 @@ buildscript {
|
|||||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||||
classpath 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.5'
|
classpath 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.5'
|
||||||
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.18.1"
|
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.19.0"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -1 +1 @@
|
|||||||
554
|
552
|
@ -1,2 +1,2 @@
|
|||||||
DO NOT TOUCH; GENERATED BY DRONE
|
DO NOT TOUCH; GENERATED BY DRONE
|
||||||
<span class="mdl-layout-title">Lint Report: 1 error and 222 warnings</span>
|
<span class="mdl-layout-title">Lint Report: 1 error and 223 warnings</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user