improved incoming activity + back to conference button.
This commit is contained in:
parent
0acbc5c65a
commit
b52433c3c3
12 changed files with 150 additions and 33 deletions
|
@ -36,8 +36,9 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<activity android:name="org.linphone.IncomingCallActivity" android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
<activity android:name="org.linphone.IncomingCallActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:launchMode="singleInstance">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
|
@ -60,7 +61,6 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="org.linphone.ConferenceActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
BIN
res/drawable/resume_blue.png
Normal file
BIN
res/drawable/resume_blue.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
|
@ -40,6 +40,7 @@
|
|||
|
||||
|
||||
<LinearLayout android:id="@+id/CallControlRow" android:layout_height="wrap_content" android:layout_width="fill_parent" android:gravity="bottom">
|
||||
<ImageButton android:id="@+id/BackToConference" android:src="@drawable/resume_blue" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="1" android:background="@drawable/clavier_bg" android:visibility="gone"/>
|
||||
<org.linphone.ui.CallButton android:id="@+id/Call"
|
||||
android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="1" android:background="@drawable/clavier_bg" android:src="@drawable/startcall_green"/>
|
||||
<org.linphone.ui.HangCallButton android:id="@+id/Decline" android:layout_height="fill_parent" android:layout_width="fill_parent"
|
||||
|
|
|
@ -39,13 +39,14 @@
|
|||
|
||||
<LinearLayout android:layout_height="wrap_content" android:orientation="vertical" android:layout_width="fill_parent">
|
||||
<LinearLayout android:id="@+id/CallControlRow" android:layout_height="fill_parent" android:layout_width="fill_parent">
|
||||
<org.linphone.ui.CallButton android:id="@+id/Call" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="0.25" android:src="@drawable/startcall_green" android:background="@drawable/clavier_bg"/>
|
||||
<org.linphone.ui.HangCallButton android:id="@+id/Decline" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="0.25" android:src="@drawable/stopcall_red" android:background="@drawable/clavier_bg"/>
|
||||
<ImageButton android:id="@+id/BackToConference" android:src="@drawable/resume_blue" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="1" android:background="@drawable/clavier_bg" android:visibility="gone"/>
|
||||
<org.linphone.ui.CallButton android:id="@+id/Call" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="1" android:src="@drawable/startcall_green" android:background="@drawable/clavier_bg"/>
|
||||
<org.linphone.ui.HangCallButton android:id="@+id/Decline" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="1" android:src="@drawable/stopcall_red" android:background="@drawable/clavier_bg"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:id="@+id/IncallControlRow" android:layout_height="fill_parent" android:layout_width="fill_parent" android:visibility="gone">
|
||||
<org.linphone.ui.AddVideoButton android:id="@+id/AddVideo" android:src="@drawable/startvideo_green" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="0.25" android:background="@drawable/clavier_bg"/>
|
||||
<org.linphone.ui.HangCallButton android:id="@+id/HangUp" android:src="@drawable/stopcall_red" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="0.25" android:background="@drawable/clavier_bg"/>
|
||||
<org.linphone.ui.AddVideoButton android:id="@+id/AddVideo" android:src="@drawable/startvideo_green" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="1" android:background="@drawable/clavier_bg"/>
|
||||
<org.linphone.ui.HangCallButton android:id="@+id/HangUp" android:src="@drawable/stopcall_red" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_weight="1" android:background="@drawable/clavier_bg"/>
|
||||
</LinearLayout>
|
||||
|
||||
<EditText android:id="@+id/status_label" android:clickable="false" android:focusable="false" android:cursorVisible="false" android:textSize="12sp" android:height="15sp" android:layout_height="wrap_content" android:layout_width="fill_parent" android:background="@android:color/transparent" android:textColor="@android:color/white" />
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</LinearLayout>
|
||||
|
||||
|
||||
<org.linphone.ui.CallButton android:id="@+id/Answer"
|
||||
<ImageButton android:id="@+id/Answer"
|
||||
android:src="@drawable/startcall_green" android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="answer"
|
||||
|
@ -36,7 +36,7 @@
|
|||
android:layout_alignParentLeft="true" android:layout_marginBottom="40sp"
|
||||
android:layout_alignParentBottom="true"/>
|
||||
|
||||
<org.linphone.ui.HangCallButton android:id="@+id/Decline"
|
||||
<ImageButton android:id="@+id/Decline"
|
||||
android:src="@drawable/stopcall_red" android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="decline"
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="waiting_for_startup">Starting up...</string>
|
||||
<string name="couldnt_accept_call">An error occurred while accepting call</string>
|
||||
|
||||
<string name="uri_picking_canceled">Canceled</string>
|
||||
<string name="error_adding_new_call">Error adding new call</string>
|
||||
<string name="transfer_started">Transfer started</string>
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.linphone.core.LinphoneCore;
|
|||
import org.linphone.core.LinphoneCoreException;
|
||||
import org.linphone.core.Log;
|
||||
import org.linphone.core.LinphoneCall.State;
|
||||
import org.linphone.mediastream.Version;
|
||||
import org.linphone.mediastream.video.capture.hwconf.Hacks;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
|
@ -123,6 +124,11 @@ public class ConferenceActivity extends ListActivity implements
|
|||
workaroundStatusBarBug();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
|
||||
protected void registerLinphoneListener(boolean register) {
|
||||
if (register)
|
||||
LinphoneManager.getInstance().addListener(this);
|
||||
|
@ -135,6 +141,8 @@ public class ConferenceActivity extends ListActivity implements
|
|||
active=true;
|
||||
registerLinphoneListener(true);
|
||||
updateConfState();
|
||||
boolean showSimpleActions = lc().getConferenceSize() == 0 && lc().getCallsNb() == 2;
|
||||
findViewById(R.id.conf_simple_merge).setVisibility(showSimpleActions ? VISIBLE : GONE);
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
|
@ -205,9 +213,6 @@ public class ConferenceActivity extends ListActivity implements
|
|||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.addCall:
|
||||
Toast.makeText(this,
|
||||
"Should now finish this activity to go back to dialer",
|
||||
Toast.LENGTH_LONG).show();
|
||||
Intent intent = new Intent().setClass(this, UriPickerActivity.class);
|
||||
intent.putExtra(UriPickerActivity.EXTRA_PICKER_TYPE, UriPickerActivity.EXTRA_PICKER_TYPE_ADD);
|
||||
startActivityForResult(intent, ID_ADD_CALL);
|
||||
|
@ -305,7 +310,6 @@ public class ConferenceActivity extends ListActivity implements
|
|||
}
|
||||
break;
|
||||
case R.id.transfer_existing:
|
||||
Toast.makeText(ConferenceActivity.this, "Transfer choice selected", Toast.LENGTH_LONG).show();
|
||||
@SuppressWarnings("unchecked") final List<LinphoneCall> existingCalls = lc().getCalls();
|
||||
existingCalls.remove(call);
|
||||
final List<String> numbers = new ArrayList<String>(existingCalls.size());
|
||||
|
@ -322,7 +326,6 @@ public class ConferenceActivity extends ListActivity implements
|
|||
new AlertDialog.Builder(ConferenceActivity.this).setAdapter(adapter, l).create().show();
|
||||
break;
|
||||
case R.id.transfer_new:
|
||||
Toast.makeText(ConferenceActivity.this, "Transfer choice selected : to do, create activity to select new call", Toast.LENGTH_LONG).show();
|
||||
Intent intent = new Intent().setClass(ConferenceActivity.this, UriPickerActivity.class);
|
||||
intent.putExtra(UriPickerActivity.EXTRA_PICKER_TYPE, UriPickerActivity.EXTRA_PICKER_TYPE_TRANSFER);
|
||||
callToTransfer = call;
|
||||
|
@ -342,7 +345,6 @@ public class ConferenceActivity extends ListActivity implements
|
|||
|
||||
public CalleeListAdapter(List<LinphoneCall> calls) {
|
||||
linphoneCalls = calls;
|
||||
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
|
@ -452,7 +454,9 @@ public class ConferenceActivity extends ListActivity implements
|
|||
setVisibility(removeFromConfButton, false);
|
||||
|
||||
final int numberOfCalls = linphoneCalls.size();
|
||||
setVisibility(v, R.id.addVideo, !isInConference && !showUnhook && numberOfCalls == 1);
|
||||
boolean showAddVideo = !isInConference && !showUnhook && numberOfCalls == 1
|
||||
&& Version.isVideoCapable() && LinphoneManager.getInstance().isVideoEnabled();
|
||||
setVisibility(v, R.id.addVideo, showAddVideo);
|
||||
|
||||
boolean statusPaused = state== State.Paused || state == State.PausedByRemote;
|
||||
setVisibility(v, R.id.callee_status_paused, statusPaused);
|
||||
|
@ -553,7 +557,7 @@ public class ConferenceActivity extends ListActivity implements
|
|||
if (!inConfC1 && inConfC2)
|
||||
return 1;
|
||||
|
||||
int durationDiff = c1.getDuration() - c2.getDuration();
|
||||
int durationDiff = c2.getDuration() - c1.getDuration();
|
||||
return durationDiff;
|
||||
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ import android.widget.Toast;
|
|||
* </ul>
|
||||
*
|
||||
*/
|
||||
public class DialerActivity extends LinphoneManagerWaitActivity implements LinphoneGuiListener, NewOutgoingCallUiListener {
|
||||
public class DialerActivity extends LinphoneManagerWaitActivity implements LinphoneGuiListener, NewOutgoingCallUiListener, OnClickListener {
|
||||
|
||||
private TextView mStatus;
|
||||
private View mHangup;
|
||||
|
@ -126,6 +126,7 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
|
|||
|
||||
|
||||
mCallControlRow = findViewById(R.id.CallControlRow);
|
||||
mCallControlRow.findViewById(R.id.BackToConference).setOnClickListener(this);
|
||||
mAddressLayout = findViewById(R.id.Addresslayout);
|
||||
|
||||
mInCallControlRow = findViewById(R.id.IncallControlRow);
|
||||
|
@ -319,7 +320,9 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
|
|||
|
||||
private void callPending(final LinphoneCall call) {
|
||||
if (getResources().getBoolean(R.bool.use_incoming_call_activity)) {
|
||||
Intent intent = new Intent().setClass(this, IncomingCallActivity.class);
|
||||
Intent intent = new Intent()
|
||||
.setClass(this, IncomingCallActivity.class)
|
||||
.putExtra("stringUri", call.getRemoteAddress().asStringUriOnly());
|
||||
startActivityForResult(intent, INCOMING_CALL_ACTIVITY);
|
||||
} else if (getResources().getBoolean(R.bool.use_incoming_call_dialog)) {
|
||||
showDialog(incomingCallDialogId);
|
||||
|
@ -462,6 +465,8 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
|
|||
mCurrentCall=null;
|
||||
}
|
||||
}
|
||||
|
||||
updateCallControlRow();
|
||||
}
|
||||
|
||||
private void showToast(int id, String txt) {
|
||||
|
@ -498,6 +503,8 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
|
|||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
updateCallControlRow();
|
||||
|
||||
// When coming back from a video call, if the phone orientation is different
|
||||
// Android will destroy the previous Dialer and create a new one.
|
||||
// Unfortunately the "call end" status event is received in the meanwhile
|
||||
|
@ -510,4 +517,38 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
|
|||
super.onResume();
|
||||
}
|
||||
|
||||
|
||||
private void updateCallControlRow() {
|
||||
if (useConferenceActivity) {
|
||||
if (LinphoneManager.isInstanciated()) {
|
||||
LinphoneCore lc = LinphoneManager.getLc();
|
||||
int calls = lc.getCallsNb();
|
||||
View backToConf = mCallControlRow.findViewById(R.id.BackToConference);
|
||||
View callButton = mCallControlRow.findViewById(R.id.Call);
|
||||
View hangButton = mCallControlRow.findViewById(R.id.Decline);
|
||||
if (calls > 0) {
|
||||
backToConf.setVisibility(View.VISIBLE);
|
||||
callButton.setVisibility(View.GONE);
|
||||
hangButton.setEnabled(true);
|
||||
} else {
|
||||
backToConf.setVisibility(View.GONE);
|
||||
callButton.setVisibility(View.VISIBLE);
|
||||
hangButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.BackToConference:
|
||||
LinphoneActivity.instance().startConferenceActivity();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,17 +20,17 @@ package org.linphone;
|
|||
|
||||
import org.linphone.core.LinphoneAddress;
|
||||
import org.linphone.core.LinphoneCall;
|
||||
import org.linphone.core.LinphoneCore;
|
||||
import org.linphone.ui.CallButton;
|
||||
import org.linphone.ui.HangCallButton;
|
||||
import org.linphone.core.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* Activity displayed when a call comes in.
|
||||
|
@ -44,11 +44,18 @@ public class IncomingCallActivity extends Activity implements OnClickListener {
|
|||
private TextView mNumberView;
|
||||
private LinphoneCall mCall;
|
||||
|
||||
private void findIncomingCall(Intent intent) {
|
||||
String stringUri = intent.getStringExtra("stringUri");
|
||||
mCall = LinphoneManager.getInstance().retrieveIncomingCall(stringUri);
|
||||
if (mCall == null) {
|
||||
Log.e("Couldn't find incoming call from ", stringUri);
|
||||
Toast.makeText(this, "Error", Toast.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
LinphoneCore lc = LinphoneManager.getLc();
|
||||
mCall = lc.getCurrentCall();
|
||||
findIncomingCall(getIntent());
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.incoming);
|
||||
|
@ -56,10 +63,8 @@ public class IncomingCallActivity extends Activity implements OnClickListener {
|
|||
mNameView = (TextView) findViewById(R.id.incoming_caller_name);
|
||||
mNumberView = (TextView) findViewById(R.id.incoming_caller_number);
|
||||
|
||||
HangCallButton hang = (HangCallButton) findViewById(R.id.Decline);
|
||||
CallButton accept = (CallButton) findViewById(R.id.Answer);
|
||||
hang.setExternalClickListener(this);
|
||||
accept.setExternalClickListener(this);
|
||||
findViewById(R.id.Decline).setOnClickListener(this);
|
||||
findViewById(R.id.Answer).setOnClickListener(this);
|
||||
|
||||
// set this flag so this activity will stay in front of the keyguard
|
||||
int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
|
||||
|
@ -67,6 +72,12 @@ public class IncomingCallActivity extends Activity implements OnClickListener {
|
|||
getWindow().addFlags(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
findIncomingCall(intent);
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
LinphoneAddress address = mCall.getRemoteAddress();
|
||||
|
@ -80,12 +91,26 @@ public class IncomingCallActivity extends Activity implements OnClickListener {
|
|||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME) {
|
||||
LinphoneManager.getLc().terminateCall(mCall);
|
||||
finish();
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.Answer:
|
||||
if (!LinphoneManager.getInstance().acceptCall(mCall)) {
|
||||
// the above method takes care of Samsung Galaxy S
|
||||
Toast.makeText(this, R.string.couldnt_accept_call, Toast.LENGTH_LONG);
|
||||
}
|
||||
break;
|
||||
case R.id.Decline:
|
||||
LinphoneManager.getLc().terminateCall(mCall);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -515,7 +515,9 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
|
|||
}
|
||||
|
||||
public void startConferenceActivity() {
|
||||
if (ConferenceActivity.active) return;
|
||||
if (ConferenceActivity.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
mHandler.post(new Runnable() {
|
||||
public void run() {
|
||||
|
|
|
@ -899,6 +899,19 @@ public final class LinphoneManager implements LinphoneCoreListener {
|
|||
return false;
|
||||
}
|
||||
|
||||
public boolean acceptCall(LinphoneCall call) {
|
||||
if (Hacks.needGalaxySAudioHack() || lpm.useGalaxySHack())
|
||||
setAudioModeIncallForGalaxyS();
|
||||
|
||||
try {
|
||||
mLc.acceptCall(call);
|
||||
return true;
|
||||
} catch (LinphoneCoreException e) {
|
||||
Log.i(e, "Accept call failed");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String extractIncomingRemoteName(Resources r, LinphoneAddress linphoneAddress) {
|
||||
if (!r.getBoolean(R.bool.show_full_remote_address_on_incoming_call))
|
||||
return extractADisplayName(r, linphoneAddress);
|
||||
|
@ -946,6 +959,7 @@ public final class LinphoneManager implements LinphoneCoreListener {
|
|||
|
||||
private static class ListenerDispatcher implements LinphoneServiceListener {
|
||||
private LinphoneServiceListener serviceListener;
|
||||
private List<LinphoneCall> incomingCalls = new ArrayList<LinphoneCall>();
|
||||
List<LinphoneSimpleListener> simpleListeners;
|
||||
public ListenerDispatcher(List<LinphoneSimpleListener> simpleListeners) {
|
||||
this.simpleListeners = simpleListeners;
|
||||
|
@ -973,8 +987,20 @@ public final class LinphoneManager implements LinphoneCoreListener {
|
|||
if (serviceListener != null) serviceListener.onCallEncryptionChanged(call, encrypted, authenticationToken);
|
||||
}
|
||||
|
||||
public LinphoneCall retrieveIncomingCall(String stringUri) {
|
||||
for (LinphoneCall call : incomingCalls) {
|
||||
if (stringUri.equals(call.getRemoteAddress().asStringUriOnly())) {
|
||||
return call;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onCallStateChanged(LinphoneCall call, State state,
|
||||
String message) {
|
||||
if (State.IncomingReceived.equals(state)) {
|
||||
incomingCalls.add(call);
|
||||
}
|
||||
if (serviceListener != null) serviceListener.onCallStateChanged(call, state, message);
|
||||
for (LinphoneOnCallStateChangedListener l : getSimpleListeners(LinphoneOnCallStateChangedListener.class)) {
|
||||
l.onCallStateChanged(call, state, message);
|
||||
|
@ -1009,11 +1035,13 @@ public final class LinphoneManager implements LinphoneCoreListener {
|
|||
public void tryingNewOutgoingCallButWrongDestinationAddress() {
|
||||
if (serviceListener != null) serviceListener.tryingNewOutgoingCallButWrongDestinationAddress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static final boolean isInstanciated() {
|
||||
return instance != null;
|
||||
}
|
||||
|
||||
public LinphoneCall retrieveIncomingCall(String stringUri) {
|
||||
return listenerDispatcher.retrieveIncomingCall(stringUri);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,9 +84,21 @@ class LinphoneCallImpl implements LinphoneCall {
|
|||
public boolean cameraEnabled() {
|
||||
return cameraEnabled(nativePtr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object call) {
|
||||
if (this == call) return true;
|
||||
if (call == null) return false;
|
||||
if (!(call instanceof LinphoneCallImpl)) return false;
|
||||
return nativePtr == ((LinphoneCallImpl)call).nativePtr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + (int) (nativePtr ^ (nativePtr >>> 32));
|
||||
return result;
|
||||
}
|
||||
public void enableEchoCancellation(boolean enable) {
|
||||
enableEchoCancellation(nativePtr,enable);
|
||||
|
||||
|
|
Loading…
Reference in a new issue