blob: cc1e54e390ee0831b4baf9c892831fdbafe7ccb7 [file] [log] [blame]
Adrien Béraud04d822c2015-04-02 17:44:36 -04001/*
Pierre Duchemin2802bfd2018-02-21 16:41:32 -05002 * Copyright (C) 2004-2018 Savoir-faire Linux Inc.
Adrien Béraud04d822c2015-04-02 17:44:36 -04003 *
Hadrien De Sousaccc947d2017-04-12 14:26:52 -04004 * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
Adrien Béraudbc0898e2018-08-23 14:31:11 -04005 * Adrien Béraud <adrien.beraud@savoirfairelinux.com>
Adrien Béraud04d822c2015-04-02 17:44:36 -04006 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
Adrien Béraudda712a42015-11-22 23:45:26 -050019 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Adrien Béraud04d822c2015-04-02 17:44:36 -040020 */
Adrien Béraud04d822c2015-04-02 17:44:36 -040021package cx.ring.fragments;
22
Adrien Béraudde0d4522018-12-12 12:11:02 -050023import android.app.Activity;
Adrien Béraud79808d02018-02-20 00:16:28 +010024import android.app.PendingIntent;
25import android.app.PictureInPictureParams;
26import android.app.RemoteAction;
Adrien Béraud162427f2016-04-12 18:04:08 -040027import android.content.Context;
28import android.content.Intent;
Kateryna Kostiukcea71192017-02-17 15:54:47 -050029import android.content.pm.ActivityInfo;
Adrien Béraudfb6341f2016-03-07 16:18:54 -050030import android.content.res.Configuration;
Adrien Béraudde0d4522018-12-12 12:11:02 -050031import android.graphics.Matrix;
Adrien Béraudfb6341f2016-03-07 16:18:54 -050032import android.graphics.PixelFormat;
Adrien Béraud79808d02018-02-20 00:16:28 +010033import android.graphics.Rect;
Adrien Béraudde0d4522018-12-12 12:11:02 -050034import android.graphics.RectF;
35import android.graphics.SurfaceTexture;
Adrien Béraud79808d02018-02-20 00:16:28 +010036import android.graphics.drawable.Icon;
Adrien Béraud8d6a4832016-04-12 18:06:11 -040037import android.os.Build;
Adrien Béraud04d822c2015-04-02 17:44:36 -040038import android.os.Bundle;
39import android.os.PowerManager;
Adrien Béraud8bc95102018-08-25 22:34:51 -040040import androidx.annotation.NonNull;
41import androidx.annotation.Nullable;
Adrien Béraudbcbb4d42018-12-08 16:06:39 -050042
43import android.text.Editable;
44import android.text.TextWatcher;
Adrien Béraudfb6341f2016-03-07 16:18:54 -050045import android.util.DisplayMetrics;
Adrien Béraud79808d02018-02-20 00:16:28 +010046import android.util.Rational;
Adrien Béraud86a1e372018-11-23 23:24:30 -050047import android.view.LayoutInflater;
Adrien Béraud162427f2016-04-12 18:04:08 -040048import android.view.Menu;
49import android.view.MenuInflater;
50import android.view.MenuItem;
Adrien Béraudde0d4522018-12-12 12:11:02 -050051import android.view.Surface;
Adrien Béraud162427f2016-04-12 18:04:08 -040052import android.view.SurfaceHolder;
Adrien Béraudde0d4522018-12-12 12:11:02 -050053import android.view.TextureView;
Adrien Béraud162427f2016-04-12 18:04:08 -040054import android.view.View;
Adrien Béraud162427f2016-04-12 18:04:08 -040055import android.view.ViewGroup;
Romain Bertozziad8fd842016-05-11 14:30:35 -040056import android.view.inputmethod.InputMethodManager;
Adrien Béraudde0d4522018-12-12 12:11:02 -050057import android.widget.FrameLayout;
Adrien Béraud162427f2016-04-12 18:04:08 -040058import android.widget.RelativeLayout;
Adrien Béraud04d822c2015-04-02 17:44:36 -040059
Pierre Ducheminbb833672018-03-09 16:18:33 -050060import com.rodolfonavalon.shaperipplelibrary.model.Circle;
Thibault Wittembergb70d5b02016-07-08 16:50:14 -040061
Adrien Béraud79808d02018-02-20 00:16:28 +010062import java.util.ArrayList;
Romain Bertozzi64a66782016-08-02 12:10:38 -040063import java.util.Locale;
Adrien Béraud79808d02018-02-20 00:16:28 +010064import java.util.Random;
Adrien Béraud04d822c2015-04-02 17:44:36 -040065
Adrien Béraud8bc95102018-08-25 22:34:51 -040066import androidx.appcompat.app.ActionBar;
67import androidx.appcompat.app.AppCompatActivity;
Adrien Béraud86a1e372018-11-23 23:24:30 -050068import androidx.databinding.DataBindingUtil;
Adrien Béraud162427f2016-04-12 18:04:08 -040069import cx.ring.R;
Adrien Béraud86a1e372018-11-23 23:24:30 -050070import cx.ring.application.RingApplication;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040071import cx.ring.call.CallPresenter;
72import cx.ring.call.CallView;
Adrien Béraudbc0898e2018-08-23 14:31:11 -040073import cx.ring.client.CallActivity;
Adrien Béraud162427f2016-04-12 18:04:08 -040074import cx.ring.client.ConversationActivity;
Thibault Wittemberg4f33d072016-10-06 17:11:41 -040075import cx.ring.client.HomeActivity;
Adrien Béraud86a1e372018-11-23 23:24:30 -050076import cx.ring.databinding.FragCallBinding;
Hadrien De Sousa73d6dd72017-09-07 10:54:51 -040077import cx.ring.dependencyinjection.RingInjectionComponent;
Adrien Béraud04d822c2015-04-02 17:44:36 -040078import cx.ring.model.CallContact;
Adrien Béraud04d822c2015-04-02 17:44:36 -040079import cx.ring.model.SipCall;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040080import cx.ring.mvp.BaseFragment;
Alexandre Lision5da46fa2017-08-09 10:25:50 -040081import cx.ring.service.DRingService;
Adrien Béraud79808d02018-02-20 00:16:28 +010082import cx.ring.services.NotificationService;
Alexandre Lision4865f7d2016-11-16 18:03:35 -050083import cx.ring.utils.ActionHelper;
Pierre Duchemin42788c32018-03-08 16:21:15 -050084import cx.ring.utils.DeviceUtils;
Romain Bertozziad8fd842016-05-11 14:30:35 -040085import cx.ring.utils.KeyboardVisibilityManager;
Adrien Béraud5f8c3f72018-03-09 14:49:39 -050086import cx.ring.utils.Log;
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -050087import cx.ring.utils.MediaButtonsHelper;
Adrien Béraud8f9d2702018-09-16 18:24:01 -040088import cx.ring.views.AvatarDrawable;
Adrien Béraud2c378b32018-07-11 15:24:02 -040089import io.reactivex.disposables.CompositeDisposable;
Adrien Béraud04d822c2015-04-02 17:44:36 -040090
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -050091public class CallFragment extends BaseFragment<CallPresenter> implements CallView, MediaButtonsHelper.MediaButtonsHelperCallback {
Adrien Béraud04d822c2015-04-02 17:44:36 -040092
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040093 public static final String TAG = CallFragment.class.getSimpleName();
Adrien Béraud04d822c2015-04-02 17:44:36 -040094
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040095 public static final String ACTION_PLACE_CALL = "PLACE_CALL";
96 public static final String ACTION_GET_CALL = "GET_CALL";
Adrien Béraud04d822c2015-04-02 17:44:36 -040097
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040098 public static final String KEY_ACTION = "action";
99 public static final String KEY_ACCOUNT_ID = "accountId";
100 public static final String KEY_CONF_ID = "confId";
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500101 public static final String KEY_AUDIO_ONLY = "AUDIO_ONLY";
Thibault Wittembergb70d5b02016-07-08 16:50:14 -0400102
Adrien Béraud86a1e372018-11-23 23:24:30 -0500103 private FragCallBinding binding;
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400104
Romain Bertozziad8fd842016-05-11 14:30:35 -0400105 private MenuItem dialPadBtn = null;
Kateryna Kostiukcea71192017-02-17 15:54:47 -0500106 private MenuItem changeScreenOrientationBtn = null;
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400107 private boolean restartVideo = false;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400108 private PowerManager.WakeLock mScreenWakeLock;
Adrien Béraud79808d02018-02-20 00:16:28 +0100109 private int mCurrentOrientation = Configuration.ORIENTATION_UNDEFINED;
Adrien Béraudde0d4522018-12-12 12:11:02 -0500110 private int mPreviewWidth = 720, mPreviewHeight = 1280;
Adrien Béraud8d6a4832016-04-12 18:06:11 -0400111
Adrien Béraud2c378b32018-07-11 15:24:02 -0400112 private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();
113
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500114 public static CallFragment newInstance(@NonNull String action, @Nullable String accountID, @Nullable String contactRingId, boolean audioOnly) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400115 Bundle bundle = new Bundle();
116 bundle.putString(KEY_ACTION, action);
117 bundle.putString(KEY_ACCOUNT_ID, accountID);
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500118 bundle.putString(ConversationFragment.KEY_CONTACT_RING_ID, contactRingId);
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500119 bundle.putBoolean(KEY_AUDIO_ONLY, audioOnly);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400120 CallFragment countDownFragment = new CallFragment();
121 countDownFragment.setArguments(bundle);
122 return countDownFragment;
123 }
Adrien Béraudfb6341f2016-03-07 16:18:54 -0500124
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400125 public static CallFragment newInstance(@NonNull String action, @Nullable String confId) {
126 Bundle bundle = new Bundle();
127 bundle.putString(KEY_ACTION, action);
128 bundle.putString(KEY_CONF_ID, confId);
129 CallFragment countDownFragment = new CallFragment();
130 countDownFragment.setArguments(bundle);
131 return countDownFragment;
132 }
133
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400134 public static int callStateToHumanState(final SipCall.State state) {
Loïc Siret0202fbf2017-09-28 16:34:28 -0400135 switch (state) {
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400136 case SEARCHING:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400137 return R.string.call_human_state_searching;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400138 case CONNECTING:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400139 return R.string.call_human_state_connecting;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400140 case RINGING:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400141 return R.string.call_human_state_ringing;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400142 case CURRENT:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400143 return R.string.call_human_state_current;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400144 case HUNGUP:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400145 return R.string.call_human_state_hungup;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400146 case BUSY:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400147 return R.string.call_human_state_busy;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400148 case FAILURE:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400149 return R.string.call_human_state_failure;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400150 case HOLD:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400151 return R.string.call_human_state_hold;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400152 case UNHOLD:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400153 return R.string.call_human_state_unhold;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400154 case OVER:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400155 return R.string.call_human_state_over;
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400156 case NONE:
Loïc Siret0202fbf2017-09-28 16:34:28 -0400157 default:
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400158 return 0;
Loïc Siret0202fbf2017-09-28 16:34:28 -0400159 }
160 }
161
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400162 @Override
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400163 protected void initPresenter(CallPresenter presenter) {
164 super.initPresenter(presenter);
165
166 String action = getArguments().getString(KEY_ACTION);
Pierre Duchemin45707ca2018-01-15 15:05:23 -0500167 if (action != null) {
168 if (action.equals(ACTION_PLACE_CALL)) {
169 presenter.initOutGoing(getArguments().getString(KEY_ACCOUNT_ID),
170 getArguments().getString(ConversationFragment.KEY_CONTACT_RING_ID),
171 getArguments().getBoolean(KEY_AUDIO_ONLY));
172 } else if (action.equals(ACTION_GET_CALL)) {
173 presenter.initIncoming(getArguments().getString(KEY_CONF_ID));
174 }
Adrien Béraudfb6341f2016-03-07 16:18:54 -0500175 }
Adrien Béraudfb6341f2016-03-07 16:18:54 -0500176 }
177
Adrien Béraud79808d02018-02-20 00:16:28 +0100178 public void onUserLeave() {
179 presenter.requestPipMode();
180 }
181
182 @Override
183 public void enterPipMode(SipCall sipCall) {
184 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
185 return;
186 }
187 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
188 PictureInPictureParams.Builder paramBuilder = new PictureInPictureParams.Builder();
Adrien Béraud86a1e372018-11-23 23:24:30 -0500189 if (binding.videoSurface.getVisibility() == View.VISIBLE) {
Adrien Béraud79808d02018-02-20 00:16:28 +0100190 int[] l = new int[2];
Adrien Béraud86a1e372018-11-23 23:24:30 -0500191 binding.videoSurface.getLocationInWindow(l);
Adrien Béraud79808d02018-02-20 00:16:28 +0100192 int x = l[0];
193 int y = l[1];
Adrien Béraud86a1e372018-11-23 23:24:30 -0500194 int w = binding.videoSurface.getWidth();
195 int h = binding.videoSurface.getHeight();
Adrien Béraud79808d02018-02-20 00:16:28 +0100196 Rect videoBounds = new Rect(x, y, x + w, y + h);
197 paramBuilder.setAspectRatio(new Rational(w, h));
198 paramBuilder.setSourceRectHint(videoBounds);
199 }
200 ArrayList<RemoteAction> actions = new ArrayList<>(1);
201 actions.add(new RemoteAction(
202 Icon.createWithResource(getContext(), R.drawable.ic_call_end_white),
203 getString(R.string.action_call_hangup),
204 getString(R.string.action_call_hangup),
205 PendingIntent.getService(getContext(), new Random().nextInt(),
206 new Intent(DRingService.ACTION_CALL_END)
207 .setClass(getContext(), DRingService.class)
208 .putExtra(NotificationService.KEY_CALL_ID, sipCall.getCallId()), PendingIntent.FLAG_ONE_SHOT)));
209 paramBuilder.setActions(actions);
210 getActivity().enterPictureInPictureMode(paramBuilder.build());
Adrien Béraud8e05cf32018-03-15 11:58:38 -0400211 } else if (DeviceUtils.isTv(getContext())) {
Adrien Béraud79808d02018-02-20 00:16:28 +0100212 getActivity().enterPictureInPictureMode();
213 }
214 }
215
Adrien Béraudfb6341f2016-03-07 16:18:54 -0500216 @Override
Hadrien De Sousa73d6dd72017-09-07 10:54:51 -0400217 public int getLayout() {
218 return R.layout.frag_call;
219 }
220
221 @Override
222 public void injectFragment(RingInjectionComponent component) {
223 component.inject(this);
224 }
225
226 @Override
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400227 public void onStart() {
228 super.onStart();
229 if (restartVideo) {
230 displayVideoSurface(true);
231 restartVideo = false;
232 }
233 }
234
235 @Override
236 public void onStop() {
237 super.onStop();
Adrien Béraud86a1e372018-11-23 23:24:30 -0500238 if (binding.videoSurface.getVisibility() == View.VISIBLE) {
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400239 restartVideo = true;
240 }
241 }
242
Adrien Béraud86a1e372018-11-23 23:24:30 -0500243 @Nullable
244 @Override
245 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
246 injectFragment(((RingApplication) getActivity().getApplication()).getRingInjectionComponent());
247 binding = DataBindingUtil.inflate(inflater, R.layout.frag_call, container, false);
248 binding.setPresenter(this);
249 return binding.getRoot();
250 }
251
Adrien Béraudde0d4522018-12-12 12:11:02 -0500252
253 private TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
254 @Override
255 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
256 configureTransform(width, height);
257 presenter.previewVideoSurfaceCreated(binding.previewSurface);
258 }
259
260 @Override
261 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
262 configureTransform(width, height);
263 }
264
265 @Override
266 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
267 presenter.previewVideoSurfaceDestroyed();
268 return true;
269 }
270
271 @Override
272 public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
273 };
274
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400275 @Override
Hadrien De Sousa73d6dd72017-09-07 10:54:51 -0400276 public void onViewCreated(View view, Bundle savedInstanceState) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400277 setHasOptionsMenu(true);
Hadrien De Sousa73d6dd72017-09-07 10:54:51 -0400278 super.onViewCreated(view, savedInstanceState);
Adrien Béraud79808d02018-02-20 00:16:28 +0100279 mCurrentOrientation = getResources().getConfiguration().orientation;
Adrien Béraud04d822c2015-04-02 17:44:36 -0400280 PowerManager powerManager = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
Adrien Béraud87810c72018-06-11 15:55:03 -0400281 mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "ring:callLock");
Adrien Béraud04d822c2015-04-02 17:44:36 -0400282 mScreenWakeLock.setReferenceCounted(false);
283
Adrien Béraud04d822c2015-04-02 17:44:36 -0400284 if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
285 mScreenWakeLock.acquire();
286 }
Adrien Béraudfb6341f2016-03-07 16:18:54 -0500287
Adrien Béraud86a1e372018-11-23 23:24:30 -0500288 binding.videoSurface.getHolder().setFormat(PixelFormat.RGBA_8888);
289 binding.videoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400290 @Override
291 public void surfaceCreated(SurfaceHolder holder) {
292 presenter.videoSurfaceCreated(holder);
293 }
294
295 @Override
296 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
297
298 }
299
300 @Override
301 public void surfaceDestroyed(SurfaceHolder holder) {
302 presenter.videoSurfaceDestroyed();
303 }
304 });
Pierre Duchemin45707ca2018-01-15 15:05:23 -0500305 view.setOnSystemUiVisibilityChangeListener(visibility -> {
306 boolean ui = (visibility & (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN)) == 0;
307 presenter.uiVisibilityChanged(ui);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400308 });
309
Adrien Béraud86a1e372018-11-23 23:24:30 -0500310 binding.shapeRipple.setRippleShape(new Circle());
311 binding.callSpeakerBtn.setChecked(presenter.isSpeakerphoneOn());
Adrien Béraudde0d4522018-12-12 12:11:02 -0500312 binding.previewSurface.setSurfaceTextureListener(listener);
Adrien Béraudbcbb4d42018-12-08 16:06:39 -0500313
314 binding.dialpadEditText.addTextChangedListener(new TextWatcher() {
315 @Override
316 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
317
318 @Override
319 public void onTextChanged(CharSequence s, int start, int before, int count) {
320 presenter.sendDtmf(s.subSequence(start, start + count));
321 }
322
323 @Override
324 public void afterTextChanged(Editable s) {}
325 });
Adrien Béraudfb6341f2016-03-07 16:18:54 -0500326 }
327
328 @Override
Adrien Béraud2c378b32018-07-11 15:24:02 -0400329 public void onDestroyView() {
330 super.onDestroyView();
331 mCompositeDisposable.clear();
Adrien Béraudfb6341f2016-03-07 16:18:54 -0500332 if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
333 mScreenWakeLock.release();
334 }
Adrien Béraud04d822c2015-04-02 17:44:36 -0400335 }
336
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400337 @Override
338 public void onConfigurationChanged(Configuration newConfig) {
339 super.onConfigurationChanged(newConfig);
Adrien Béraud79808d02018-02-20 00:16:28 +0100340 int newOrientation = newConfig.orientation;
341 if (newOrientation == mCurrentOrientation) {
342 return;
343 }
344 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getActivity().isInPictureInPictureMode()) {
345 // avoid restarting the camera when entering PIP mode
346 return;
347 }
348 mCurrentOrientation = newOrientation;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400349 presenter.configurationChanged();
Adrien Béraudb179bab2015-10-08 12:04:22 -0400350 }
351
Adrien Béraud04d822c2015-04-02 17:44:36 -0400352 @Override
353 public void onCreateOptionsMenu(Menu m, MenuInflater inf) {
Adrien Béraudda712a42015-11-22 23:45:26 -0500354 super.onCreateOptionsMenu(m, inf);
Adrien Béraudda2eb1a2016-01-11 13:15:12 -0500355 inf.inflate(R.menu.ac_call, m);
Romain Bertozziad8fd842016-05-11 14:30:35 -0400356 dialPadBtn = m.findItem(R.id.menuitem_dialpad);
Kateryna Kostiukcea71192017-02-17 15:54:47 -0500357 changeScreenOrientationBtn = m.findItem(R.id.menuitem_change_screen_orientation);
Adrien Béraudda712a42015-11-22 23:45:26 -0500358 }
359
360 @Override
361 public void onPrepareOptionsMenu(Menu menu) {
362 super.onPrepareOptionsMenu(menu);
Hadrien De Sousaf5774ee2017-05-17 14:04:17 -0400363 presenter.prepareOptionMenu();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400364 }
365
366 @Override
367 public boolean onOptionsItemSelected(MenuItem item) {
368 super.onOptionsItemSelected(item);
369 switch (item.getItemId()) {
370 case android.R.id.home:
371 case R.id.menuitem_chat:
Hadrien De Sousaf5774ee2017-05-17 14:04:17 -0400372 presenter.chatClick();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400373 break;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400374 case R.id.menuitem_dialpad:
Hadrien De Sousaf5774ee2017-05-17 14:04:17 -0400375 presenter.dialpadClick();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400376 break;
377 case R.id.menuitem_change_screen_orientation:
Hadrien De Sousaf5774ee2017-05-17 14:04:17 -0400378 presenter.screenRotationClick();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400379 break;
380 }
381 return true;
382 }
383
Adrien Béraud79808d02018-02-20 00:16:28 +0100384
385 @Override
386 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400387 if (isInPictureInPictureMode)
388 ((CallActivity) getActivity()).getSupportActionBar().hide();
389 else
390 ((CallActivity) getActivity()).getSupportActionBar().show();
Adrien Béraud79808d02018-02-20 00:16:28 +0100391 presenter.pipModeChanged(isInPictureInPictureMode);
392 }
393
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400394 @Override
395 public void blockScreenRotation() {
Adrien Béraudace37a22018-06-22 14:58:36 -0400396 getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400397 }
398
399 @Override
400 public void displayContactBubble(final boolean display) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500401 binding.contactBubbleLayout.getHandler().post(() -> binding.contactBubbleLayout.setVisibility(display ? View.VISIBLE : View.GONE));
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400402 }
403
404 @Override
405 public void displayVideoSurface(final boolean display) {
Adrien Béraudde0d4522018-12-12 12:11:02 -0500406 Log.w(TAG, "displayVideoSurface " + display);
Adrien Béraud86a1e372018-11-23 23:24:30 -0500407 binding.videoSurface.setVisibility(display ? View.VISIBLE : View.GONE);
Adrien Béraudde0d4522018-12-12 12:11:02 -0500408 //binding.previewSurface.setVisibility(display ? View.VISIBLE : View.GONE);
Adrien Béraudace37a22018-06-22 14:58:36 -0400409 updateMenu();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400410 }
411
412 @Override
Adrien Béraud79808d02018-02-20 00:16:28 +0100413 public void displayPreviewSurface(final boolean display) {
414 if (display) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500415 binding.videoSurface.setZOrderOnTop(false);
Adrien Béraudde0d4522018-12-12 12:11:02 -0500416 //binding.previewSurface.setZOrderMediaOverlay(true);
Adrien Béraud86a1e372018-11-23 23:24:30 -0500417 binding.videoSurface.setZOrderMediaOverlay(false);
Adrien Béraud79808d02018-02-20 00:16:28 +0100418 } else {
Adrien Béraudde0d4522018-12-12 12:11:02 -0500419 //binding.previewSurface.setZOrderMediaOverlay(false);
Adrien Béraud86a1e372018-11-23 23:24:30 -0500420 binding.videoSurface.setZOrderMediaOverlay(true);
421 binding.videoSurface.setZOrderOnTop(true);
Adrien Béraud79808d02018-02-20 00:16:28 +0100422 }
423 }
424
425 @Override
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400426 public void displayHangupButton(boolean display) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500427 binding.callControlGroup.setVisibility(display ? View.VISIBLE : View.GONE);
428 binding.callHangupBtn.setVisibility(display ? View.VISIBLE : View.GONE);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400429 }
430
431 @Override
432 public void displayDialPadKeyboard() {
Adrien Béraudbcbb4d42018-12-08 16:06:39 -0500433 KeyboardVisibilityManager.showKeyboard(getActivity(), binding.dialpadEditText, InputMethodManager.SHOW_FORCED);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400434 }
435
436 @Override
437 public void switchCameraIcon(boolean isFront) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500438 binding.callCameraFlipBtn.setImageResource(isFront ? R.drawable.ic_camera_front_white : R.drawable.ic_camera_rear_white);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400439 }
440
441 @Override
Adrien Béraudd3bf4e42018-02-14 14:44:41 +0100442 public void updateMenu() {
443 getActivity().invalidateOptionsMenu();
444 }
445
446 @Override
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400447 public void changeScreenRotation() {
Adrien Béraud79808d02018-02-20 00:16:28 +0100448 if (mCurrentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400449 getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
450 } else {
451 getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
452 }
453 }
454
455 @Override
456 public void updateTime(final long duration) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500457 if (binding.callStatusTxt != null)
458 binding.callStatusTxt.setText(String.format(Locale.getDefault(), "%d:%02d:%02d", duration / 3600, duration % 3600 / 60, duration % 60));
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400459 }
460
461 @Override
Adrien Beraud8e6e2ff2017-06-14 14:40:27 +0200462 public void updateContactBubble(@NonNull final CallContact contact) {
Adrien Béraud5f8c3f72018-03-09 14:49:39 -0500463 String username = contact.getRingUsername();
464 String displayName = contact.getDisplayName();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400465
Adrien Béraud5f8c3f72018-03-09 14:49:39 -0500466 String ringId = contact.getIds().get(0);
Adrien Béraudafcdd962018-07-06 21:38:41 -0400467 Log.d(TAG, "updateContactBubble: contact=" + contact + " username=" + username + ", ringId=" + ringId);
Adrien Béraud5f8c3f72018-03-09 14:49:39 -0500468
Adrien Béraudafcdd962018-07-06 21:38:41 -0400469 boolean hasProfileName = displayName != null && !displayName.contentEquals(username);
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400470
471 AppCompatActivity activity = (AppCompatActivity) getActivity();
472 if (activity != null) {
473 ActionBar ab = activity.getSupportActionBar();
474 if (ab != null) {
475 if (hasProfileName) {
476 ab.setTitle(displayName);
477 ab.setSubtitle(username);
478 } else {
479 ab.setTitle(username);
480 ab.setSubtitle(null);
481 }
482 ab.setDisplayShowTitleEnabled(true);
483 }
484 }
485
Adrien Béraudafcdd962018-07-06 21:38:41 -0400486 if (hasProfileName) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500487 binding.contactBubbleNumTxt.setVisibility(View.VISIBLE);
488 binding.contactBubbleTxt.setText(displayName);
489 binding.contactBubbleNumTxt.setText(username);
Adrien Béraudafcdd962018-07-06 21:38:41 -0400490 } else {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500491 binding.contactBubbleNumTxt.setVisibility(View.GONE);
492 binding.contactBubbleTxt.setText(username);
Adrien Béraudafcdd962018-07-06 21:38:41 -0400493 }
Adrien Béraud5f8c3f72018-03-09 14:49:39 -0500494
Adrien Béraud86a1e372018-11-23 23:24:30 -0500495 binding.contactBubble.setImageDrawable(new AvatarDrawable(getActivity(), contact));
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400496 }
497
498 @Override
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400499 public void updateCallStatus(final SipCall.State callState) {
Pierre Duchemin45707ca2018-01-15 15:05:23 -0500500 getActivity().runOnUiThread(() -> {
501 switch (callState) {
Adrien Bérauda3f0b4b2018-08-09 11:41:53 -0400502 case NONE:
Adrien Béraud86a1e372018-11-23 23:24:30 -0500503 binding.callStatusTxt.setText("");
Pierre Duchemin45707ca2018-01-15 15:05:23 -0500504 break;
505 default:
Adrien Béraud86a1e372018-11-23 23:24:30 -0500506 binding.callStatusTxt.setText(callStateToHumanState(callState));
Pierre Duchemin45707ca2018-01-15 15:05:23 -0500507 break;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400508 }
509 });
510 }
511
512 @Override
513 public void initMenu(boolean isSpeakerOn, boolean hasContact, boolean displayFlip, boolean canDial, boolean onGoingCall) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500514 if (binding.callCameraFlipBtn != null) {
515 binding.callCameraFlipBtn.setVisibility(displayFlip ? View.VISIBLE : View.GONE);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400516 }
Romain Bertozziad8fd842016-05-11 14:30:35 -0400517 if (dialPadBtn != null) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400518 dialPadBtn.setVisible(canDial);
Romain Bertozziad8fd842016-05-11 14:30:35 -0400519 }
Kateryna Kostiukcea71192017-02-17 15:54:47 -0500520 if (changeScreenOrientationBtn != null) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500521 changeScreenOrientationBtn.setVisible(binding.videoSurface.getVisibility() == View.VISIBLE);
Kateryna Kostiukcea71192017-02-17 15:54:47 -0500522 }
Adrien Béraud04d822c2015-04-02 17:44:36 -0400523 }
524
525 @Override
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400526 public void initNormalStateDisplay(final boolean audioOnly, boolean isSpeakerphoneOn) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500527 binding.shapeRipple.stopRipple();
Pierre Ducheminbb833672018-03-09 16:18:33 -0500528
Adrien Béraud86a1e372018-11-23 23:24:30 -0500529 binding.callAcceptBtn.setVisibility(View.GONE);
530 binding.callRefuseBtn.setVisibility(View.GONE);
531 binding.callControlGroup.setVisibility(View.VISIBLE);
532 binding.callHangupBtn.setVisibility(View.VISIBLE);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400533
Adrien Béraud86a1e372018-11-23 23:24:30 -0500534 binding.contactBubbleLayout.setVisibility(audioOnly ? View.VISIBLE : View.GONE);
535 binding.callSpeakerBtn.setChecked(isSpeakerphoneOn);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400536
Adrien Béraudace37a22018-06-22 14:58:36 -0400537 getActivity().invalidateOptionsMenu();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400538 }
539
540 @Override
541 public void initIncomingCallDisplay() {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500542 binding.callAcceptBtn.setVisibility(View.VISIBLE);
543 binding.callRefuseBtn.setVisibility(View.VISIBLE);
544 binding.callControlGroup.setVisibility(View.GONE);
545 binding.callHangupBtn.setVisibility(View.GONE);
Hadrien De Sousa841d15a2017-06-02 11:52:10 -0400546
Adrien Béraud86a1e372018-11-23 23:24:30 -0500547 binding.contactBubbleLayout.setVisibility(View.VISIBLE);
Adrien Béraudace37a22018-06-22 14:58:36 -0400548 getActivity().invalidateOptionsMenu();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400549 }
550
551 @Override
552 public void initOutGoingCallDisplay() {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500553 binding.callAcceptBtn.setVisibility(View.GONE);
554 binding.callRefuseBtn.setVisibility(View.VISIBLE);
555 binding.callControlGroup.setVisibility(View.GONE);
556 binding.callHangupBtn.setVisibility(View.GONE);
Hadrien De Sousa841d15a2017-06-02 11:52:10 -0400557
Adrien Béraud86a1e372018-11-23 23:24:30 -0500558 binding.contactBubbleLayout.setVisibility(View.VISIBLE);
Adrien Béraudace37a22018-06-22 14:58:36 -0400559 getActivity().invalidateOptionsMenu();
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400560 }
561
562 @Override
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400563 public void resetVideoSize(final int videoWidth, final int videoHeight, final int previewWidth, final int previewHeight) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400564 ViewGroup rootView = (ViewGroup) getView();
565 if (rootView == null)
566 return;
Adrien Béraud8d6a4832016-04-12 18:06:11 -0400567
Adrien Béraudace37a22018-06-22 14:58:36 -0400568 double videoRatio = videoWidth / (double) videoHeight;
569 double screenRatio = getView().getWidth() / (double) getView().getHeight();
Adrien Béraud8d6a4832016-04-12 18:06:11 -0400570
Adrien Béraud86a1e372018-11-23 23:24:30 -0500571 RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.videoSurface.getLayoutParams();
Adrien Béraudace37a22018-06-22 14:58:36 -0400572 int oldW = params.width;
573 int oldH = params.height;
574 if (videoRatio >= screenRatio) {
575 params.width = RelativeLayout.LayoutParams.MATCH_PARENT;
576 params.height = (int) (videoHeight * (double) rootView.getWidth() / (double) videoWidth);
577 } else {
578 params.height = RelativeLayout.LayoutParams.MATCH_PARENT;
579 params.width = (int) (videoWidth * (double) rootView.getHeight() / (double) videoHeight);
580 }
Thibault Wittembergb3280502016-12-16 17:34:53 -0500581
Adrien Béraudace37a22018-06-22 14:58:36 -0400582 if (oldW != params.width || oldH != params.height) {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500583 binding.videoSurface.setLayoutParams(params);
Adrien Béraudace37a22018-06-22 14:58:36 -0400584 }
Adrien Béraud04d822c2015-04-02 17:44:36 -0400585
Adrien Béraud30532a52018-09-11 15:10:05 -0400586 if (previewWidth == -1 && previewHeight == -1)
587 return;
Adrien Béraudde0d4522018-12-12 12:11:02 -0500588 mPreviewWidth = previewWidth;
589 mPreviewHeight = previewHeight;
Adrien Béraud30532a52018-09-11 15:10:05 -0400590 Log.w(TAG, "resetVideoSize preview: " + previewWidth + "x" + previewHeight);
Adrien Béraudde0d4522018-12-12 12:11:02 -0500591 }
Adrien Béraud30532a52018-09-11 15:10:05 -0400592
Adrien Béraudde0d4522018-12-12 12:11:02 -0500593 private void configureTransform(int viewWidth, int viewHeight) {
594 Activity activity = getActivity();
595 if (null == binding.previewSurface|| null == activity) {
596 return;
Adrien Béraud30532a52018-09-11 15:10:05 -0400597 }
Adrien Béraudde0d4522018-12-12 12:11:02 -0500598 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
599 boolean rot = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation;
600 Log.w(TAG, "configureTransform " + viewWidth + "x" + viewHeight + " rot="+rot + " mPreviewWidth="+mPreviewWidth + " mPreviewHeight="+mPreviewHeight);
601 Matrix matrix = new Matrix();
602 RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
603 float centerX = viewRect.centerX();
604 float centerY = viewRect.centerY();
605 if (rot) {
606 RectF bufferRect = new RectF(0, 0, mPreviewHeight, mPreviewWidth);
607 bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
608 matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
609 float scale = Math.max(
610 (float) viewHeight / mPreviewHeight,
611 (float) viewWidth / mPreviewWidth);
612 matrix.postScale(scale, scale, centerX, centerY);
613 matrix.postRotate(90 * (rotation - 2), centerX, centerY);
614 } else if (Surface.ROTATION_180 == rotation) {
615 matrix.postRotate(180, centerX, centerY);
Adrien Béraudace37a22018-06-22 14:58:36 -0400616 }
Adrien Béraudde0d4522018-12-12 12:11:02 -0500617 binding.previewSurface.setTransform(matrix);
Adrien Béraud04d822c2015-04-02 17:44:36 -0400618 }
619
620 @Override
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500621 public void goToConversation(String accountId, String conversationId) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400622 Intent intent = new Intent();
Pierre Duchemin42788c32018-03-08 16:21:15 -0500623 if (DeviceUtils.isTablet(getActivity())) {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400624 intent.setClass(getActivity(), HomeActivity.class)
Alexandre Lision5da46fa2017-08-09 10:25:50 -0400625 .setAction(DRingService.ACTION_CONV_ACCEPT)
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500626 .putExtra(ConversationFragment.KEY_CONTACT_RING_ID, conversationId);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400627 startActivity(intent);
Adrien Béraudda712a42015-11-22 23:45:26 -0500628 } else {
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400629 intent.setClass(getActivity(), ConversationActivity.class)
630 .setAction(Intent.ACTION_VIEW)
631 .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP)
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500632 .putExtra(ConversationFragment.KEY_ACCOUNT_ID, accountId)
633 .putExtra(ConversationFragment.KEY_CONTACT_RING_ID, conversationId);
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400634 startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION);
Adrien Béraudda712a42015-11-22 23:45:26 -0500635 }
Adrien Béraud04d822c2015-04-02 17:44:36 -0400636 }
637
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400638 @Override
639 public void goToAddContact(CallContact callContact) {
640 startActivityForResult(ActionHelper.getAddNumberIntentForContact(callContact),
641 ConversationFragment.REQ_ADD_CONTACT);
642 }
643
644 @Override
645 public void finish() {
646 getActivity().finish();
Alexandre Lision1532d952016-11-17 09:41:00 -0500647 }
648
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400649 public void speakerClicked() {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500650 presenter.speakerClick(binding.callSpeakerBtn.isChecked());
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400651 }
652
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400653 public void micClicked() {
Adrien Béraud86a1e372018-11-23 23:24:30 -0500654 presenter.muteMicrophoneToggled(binding.callMicBtn.isChecked());
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400655 }
656
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400657 public void hangUpClicked() {
Hadrien De Sousaf5774ee2017-05-17 14:04:17 -0400658 presenter.hangupCall();
Alexandre Lision1532d952016-11-17 09:41:00 -0500659 }
660
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400661 public void refuseClicked() {
Hadrien De Sousaf5774ee2017-05-17 14:04:17 -0400662 presenter.refuseCall();
Thibault Wittembergb70d5b02016-07-08 16:50:14 -0400663 }
664
Thibault Wittembergb70d5b02016-07-08 16:50:14 -0400665 public void acceptClicked() {
Hadrien De Sousaf5774ee2017-05-17 14:04:17 -0400666 presenter.acceptCall();
Thibault Wittembergb70d5b02016-07-08 16:50:14 -0400667 }
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500668
Adrien Béraudbc0898e2018-08-23 14:31:11 -0400669 public void cameraFlip() {
670 presenter.switchVideoInputClick();
671 }
672
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500673 @Override
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500674 public void positiveMediaButtonClicked() {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500675 presenter.positiveButtonClicked();
676 }
677
678 @Override
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500679 public void negativeMediaButtonClicked() {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500680 presenter.negativeButtonClicked();
681 }
682
683 @Override
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500684 public void toggleMediaButtonClicked() {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500685 presenter.toggleButtonClicked();
686 }
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400687}