blob: 52d813e5b33263884e8758b7a894cc026f6bef5d [file] [log] [blame]
Loïc Siret0202fbf2017-09-28 16:34:28 -04001/*
Pierre Duchemin2802bfd2018-02-21 16:41:32 -05002 * Copyright (C) 2004-2018 Savoir-faire Linux Inc.
Loïc Siret0202fbf2017-09-28 16:34:28 -04003 *
Adrien Béraud64538722018-03-15 17:34:00 -04004 * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
5 *
Loïc Siret0202fbf2017-09-28 16:34:28 -04006 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
Aline Bonnet9c6defe2016-12-21 13:21:02 -050020package cx.ring.fragments;
21
Pierre Duchemin4af6b392018-02-02 11:23:56 -050022import android.Manifest;
Adrien Béraud1b8ac9f2018-05-31 14:34:23 -040023import android.app.DownloadManager;
Adrien Béraudae45ffa2018-07-06 16:21:29 -040024import android.content.ClipData;
Adrien Béraud1b8ac9f2018-05-31 14:34:23 -040025import android.content.Context;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050026import android.content.Intent;
Pierre Duchemine0c3a492018-02-01 16:22:45 -050027import android.media.MediaScannerConnection;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050028import android.os.Bundle;
Adrien Béraud64538722018-03-15 17:34:00 -040029import android.provider.MediaStore;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050030import android.support.design.widget.Snackbar;
Pierre Duchemin4af6b392018-02-02 11:23:56 -050031import android.support.v4.app.ActivityCompat;
Adrien Béraud64538722018-03-15 17:34:00 -040032import android.support.v4.content.FileProvider;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050033import android.support.v7.app.ActionBar;
34import android.support.v7.app.AlertDialog;
Aline Bonnete8e87f62016-12-26 10:54:37 -050035import android.support.v7.app.AppCompatActivity;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050036import android.support.v7.widget.DefaultItemAnimator;
37import android.support.v7.widget.LinearLayoutManager;
38import android.support.v7.widget.RecyclerView;
Pierre Duchemine0c3a492018-02-01 16:22:45 -050039import android.util.Log;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050040import android.view.Menu;
41import android.view.MenuInflater;
42import android.view.MenuItem;
43import android.view.View;
44import android.view.ViewGroup;
45import android.view.inputmethod.EditorInfo;
Pierre Duchemin17542a72018-02-14 16:38:36 -050046import android.widget.ImageButton;
47import android.widget.ProgressBar;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050048import android.widget.Spinner;
Pierre Duchemin1fa61922018-01-18 10:09:02 -050049import android.widget.TextView;
Pierre Duchemine0c3a492018-02-01 16:22:45 -050050import android.widget.Toast;
51
52import java.io.File;
53import java.io.IOException;
Adrien Béraud64538722018-03-15 17:34:00 -040054import java.text.SimpleDateFormat;
55import java.util.Date;
Adrien Béraudace37a22018-06-22 14:58:36 -040056import java.util.List;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050057
Aline Bonnet9c6defe2016-12-21 13:21:02 -050058import butterknife.BindView;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050059import butterknife.OnClick;
60import butterknife.OnEditorAction;
Adrien Béraud5b47cb52018-07-06 16:24:14 -040061import cx.ring.BuildConfig;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050062import cx.ring.R;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050063import cx.ring.adapters.ConversationAdapter;
64import cx.ring.adapters.NumberAdapter;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050065import cx.ring.client.CallActivity;
Aline Bonnet830c4c62016-12-27 14:02:02 -050066import cx.ring.client.ConversationActivity;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050067import cx.ring.client.HomeActivity;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040068import cx.ring.conversation.ConversationPresenter;
69import cx.ring.conversation.ConversationView;
Hadrien De Sousa73d6dd72017-09-07 10:54:51 -040070import cx.ring.dependencyinjection.RingInjectionComponent;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050071import cx.ring.model.CallContact;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050072import cx.ring.model.Conversation;
Adrien Béraudace37a22018-06-22 14:58:36 -040073import cx.ring.model.ConversationElement;
Adrien Béraud21533292018-03-22 14:11:35 -040074import cx.ring.model.DataTransfer;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050075import cx.ring.model.Phone;
Pierre Duchemine0c3a492018-02-01 16:22:45 -050076import cx.ring.model.RingError;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050077import cx.ring.model.Uri;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040078import cx.ring.mvp.BaseFragment;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040079import cx.ring.services.NotificationService;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050080import cx.ring.utils.ActionHelper;
Adrien Béraud64538722018-03-15 17:34:00 -040081import cx.ring.utils.AndroidFileUtils;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050082import cx.ring.utils.ClipboardHelper;
Adrien Béraud64538722018-03-15 17:34:00 -040083import cx.ring.utils.ContentUriHandler;
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -050084import cx.ring.utils.MediaButtonsHelper;
Adrien Béraud9d4a81f2018-07-09 01:19:52 -040085import cx.ring.views.MessageEditText;
Aline Bonnet9c6defe2016-12-21 13:21:02 -050086
Pierre Duchemine0c3a492018-02-01 16:22:45 -050087import static android.app.Activity.RESULT_OK;
88
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040089public class ConversationFragment extends BaseFragment<ConversationPresenter> implements
Aline Bonnet9c6defe2016-12-21 13:21:02 -050090 Conversation.ConversationActionCallback,
91 ClipboardHelper.ClipboardHelperCallback,
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -050092 MediaButtonsHelper.MediaButtonsHelperCallback,
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -040093 ConversationView {
Aline Bonnet9c6defe2016-12-21 13:21:02 -050094
Adrien Béraud64538722018-03-15 17:34:00 -040095 protected static final String TAG = ConversationFragment.class.getSimpleName();
96
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040097 public static final int REQ_ADD_CONTACT = 42;
Hadrien De Sousaccc947d2017-04-12 14:26:52 -040098
Adrien Béraud5b47cb52018-07-06 16:24:14 -040099 public static final String KEY_CONTACT_RING_ID = BuildConfig.APPLICATION_ID + "CONTACT_RING_ID";
100 public static final String KEY_ACCOUNT_ID = BuildConfig.APPLICATION_ID + "ACCOUNT_ID";
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500101
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500102 private static final String CONVERSATION_DELETE = "CONVERSATION_DELETE";
103
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500104 private static final int REQUEST_CODE_FILE_PICKER = 1000;
Pierre Duchemin4af6b392018-02-02 11:23:56 -0500105 private static final int REQUEST_PERMISSION_CAMERA = 1001;
Adrien Béraud64538722018-03-15 17:34:00 -0400106 private static final int REQUEST_CODE_TAKE_PICTURE = 1002;
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500107
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400108 @BindView(R.id.msg_input_txt)
Adrien Béraud9d4a81f2018-07-09 01:19:52 -0400109 protected MessageEditText mMsgEditTxt;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400110
111 @BindView(R.id.ongoingcall_pane)
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400112 protected ViewGroup mTopPane;
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400113
114 @BindView(R.id.hist_list)
115 protected RecyclerView mHistList;
116
117 @BindView(R.id.number_selector)
118 protected Spinner mNumberSpinner;
119
Pierre Duchemin17542a72018-02-14 16:38:36 -0500120 @BindView(R.id.pb_data_transfer)
121 protected ProgressBar pbDataTransfer;
122
123 @BindView(R.id.send_data)
124 protected ImageButton sendData;
125
Adrien Béraud64538722018-03-15 17:34:00 -0400126 @BindView(R.id.btn_take_picture)
127 protected ImageButton takePicture;
128
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500129 @BindView(R.id.cvMessageInput)
130 protected View mMessageInput;
131
Adrien Béraudace37a22018-06-22 14:58:36 -0400132 @BindView(R.id.unknownContactPrompt)
133 protected View mUnknownPrompt;
134
135 @BindView(R.id.trustRequestPrompt)
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500136 protected View mTrustRequestPrompt;
137
Adrien Béraudace37a22018-06-22 14:58:36 -0400138 @BindView(R.id.trustRequestMessageLayout)
139 protected View mTrustRequestMessageLayout;
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500140
141 @BindView(R.id.tvTrustRequestMessage)
142 protected TextView mTvTrustRequestMessage;
143
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500144 private AlertDialog mDeleteDialog;
145 private boolean mDeleteConversation = false;
146
Adrien Béraud5b47cb52018-07-06 16:24:14 -0400147 private MenuItem mAudioCallBtn = null;
148 private MenuItem mVideoCallBtn = null;
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500149
150 private ConversationAdapter mAdapter = null;
151 private NumberAdapter mNumberAdapter = null;
152
Adrien Béraud64538722018-03-15 17:34:00 -0400153 private File mCurrentPhoto = null;
154
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500155 private static int getIndex(Spinner spinner, Uri myString) {
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500156 for (int i = 0, n = spinner.getCount(); i < n; i++)
157 if (((Phone) spinner.getItemAtPosition(i)).getNumber().equals(myString)) {
158 return i;
159 }
160 return 0;
161 }
162
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400163 @Override
Adrien Béraudace37a22018-06-22 14:58:36 -0400164 public void refreshView(final List<ConversationElement> conversation) {
Adrien Béraud20d42212018-03-02 14:40:38 -0500165 if (conversation == null) {
166 return;
167 }
Adrien Béraudace37a22018-06-22 14:58:36 -0400168 if (mAdapter != null) {
169 mAdapter.updateDataset(conversation);
170 }
171 getActivity().invalidateOptionsMenu();
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500172 }
173
Aline Bonnete8e87f62016-12-26 10:54:37 -0500174 @Override
Adrien Béraud64538722018-03-15 17:34:00 -0400175 public void scrollToEnd() {
Adrien Béraudace37a22018-06-22 14:58:36 -0400176 if (mAdapter.getItemCount() > 0) {
177 mHistList.smoothScrollToPosition(mAdapter.getItemCount() - 1);
178 }
Adrien Béraud64538722018-03-15 17:34:00 -0400179 }
180
181 @Override
Hadrien De Sousa73d6dd72017-09-07 10:54:51 -0400182 public int getLayout() {
183 return R.layout.frag_conversation;
184 }
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500185
Hadrien De Sousa73d6dd72017-09-07 10:54:51 -0400186 @Override
187 public void injectFragment(RingInjectionComponent component) {
188 component.inject(this);
189 }
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500190
Hadrien De Sousa73d6dd72017-09-07 10:54:51 -0400191 @Override
192 public void onViewCreated(View view, Bundle savedInstanceState) {
193 super.onViewCreated(view, savedInstanceState);
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500194
Adrien Béraud9d4a81f2018-07-09 01:19:52 -0400195 mMsgEditTxt.setMediaListener(contentInfo -> {
196 try {
197 presenter.sendFile(AndroidFileUtils.getCacheFile(getActivity(), contentInfo.getContentUri()));
198 contentInfo.releasePermission();
199 } catch (IOException e) {
200 e.printStackTrace();
201 }
202 });
203
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400204 if (mTopPane != null) {
205 mTopPane.setVisibility(View.GONE);
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500206 }
207
208 LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
209 mLayoutManager.setStackFromEnd(true);
210
211 if (mHistList != null) {
212 mHistList.setLayoutManager(mLayoutManager);
Adrien Béraudace37a22018-06-22 14:58:36 -0400213 mHistList.setAdapter(null);
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500214 mHistList.setItemAnimator(new DefaultItemAnimator());
215 }
216
217 // reload delete conversation state (before rotation)
218 mDeleteConversation = savedInstanceState != null && savedInstanceState.getBoolean(CONVERSATION_DELETE);
219
220 setHasOptionsMenu(true);
Alexandre Lision62c69782017-04-04 17:26:44 -0400221 if (mHistList != null) {
222 mHistList.setAdapter(mAdapter);
223 }
224
225 if (mDeleteConversation) {
226 presenter.deleteAction();
Aline Bonnete8e87f62016-12-26 10:54:37 -0500227 }
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500228 }
229
Adrien Béraud7c505112018-03-20 16:03:28 -0400230 @Override
231 public boolean onContextItemSelected(MenuItem item) {
232 if (mAdapter.onContextItemSelected(item))
233 return true;
234 return super.onContextItemSelected(item);
235 }
236
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500237 @OnClick(R.id.msg_send)
Alexandre Lision66a9f9a2017-02-26 20:28:27 -0500238 public void sendMessageText() {
Adrien Béraudace37a22018-06-22 14:58:36 -0400239 String message = mMsgEditTxt.getText().toString();
240 clearMsgEdit();
241 presenter.sendTextMessage(message);
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500242 }
243
Pierre Duchemin17542a72018-02-14 16:38:36 -0500244 @OnClick(R.id.send_data)
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500245 public void selectFile() {
246 presenter.selectFile();
247 }
248
Adrien Béraud64538722018-03-15 17:34:00 -0400249 private File createImageFile() throws IOException {
250 // Create an image file name
251 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
252 String imageFileName = "img_" + timeStamp + "_";
253
254 // Save a file: path for use with ACTION_VIEW intents
255 return File.createTempFile(imageFileName, ".jpg", getActivity().getExternalCacheDir());
256 }
257
258 @OnClick(R.id.btn_take_picture)
259 public void takePicture() {
260 Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
261 if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
262
263 // Create the File where the photo should go
264 File photoFile = null;
265 try {
266 photoFile = createImageFile();
267 } catch (IOException ex) {
268 Log.e(TAG, "takePicture: error creating temporary file", ex);
269 return;
270 }
271 Log.i(TAG, "takePicture: trying to save to " + photoFile);
272 mCurrentPhoto = photoFile;
273 android.net.Uri photoURI = FileProvider.getUriForFile(getActivity(),
274 ContentUriHandler.AUTHORITY_FILES,
275 photoFile);
276 takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
277 startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PICTURE);
278 }
279 }
280
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500281 @Override
Pierre Duchemin4af6b392018-02-02 11:23:56 -0500282 public void askWriteExternalStoragePermission() {
283 ActivityCompat.requestPermissions(getActivity(),
284 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
285 REQUEST_PERMISSION_CAMERA);
286 }
287
288 @Override
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500289 public void openFilePicker() {
290 Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
291 intent.addCategory(Intent.CATEGORY_OPENABLE);
292 intent.setType("*/*");
293
294 startActivityForResult(intent, REQUEST_CODE_FILE_PICKER);
295 }
296
297 @Override
Pierre Duchemin17542a72018-02-14 16:38:36 -0500298 public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
Adrien Béraud64538722018-03-15 17:34:00 -0400299 Log.w(TAG, "onActivityResult: " + requestCode + " " + resultCode + " " + resultData);
Pierre Duchemin17542a72018-02-14 16:38:36 -0500300 super.onActivityResult(requestCode, resultCode, resultData);
301
302 if (requestCode == REQUEST_CODE_FILE_PICKER && resultCode == RESULT_OK) {
Adrien Béraud64538722018-03-15 17:34:00 -0400303 if (resultData == null) {
304 return;
Pierre Duchemin17542a72018-02-14 16:38:36 -0500305 }
Adrien Béraud64538722018-03-15 17:34:00 -0400306 android.net.Uri uri = resultData.getData();
307 if (uri == null) {
308 return;
309 }
310 setLoading(true);
311 new Thread(() -> {
312 try {
313 File cacheFile = AndroidFileUtils.getCacheFile(getActivity(), uri);
314 presenter.sendFile(cacheFile);
315 } catch (IOException e) {
316 Log.e(TAG, "onActivityResult: not able to create cache file");
317 getActivity().runOnUiThread(() -> displayErrorToast(RingError.INVALID_FILE));
318 }
319 getActivity().runOnUiThread(() -> setLoading(false));
320 }).start();
321 } else if (requestCode == REQUEST_CODE_TAKE_PICTURE) {
322 if (resultCode != RESULT_OK) {
323 mCurrentPhoto = null;
324 return;
325 }
326 Log.w(TAG, "onActivityResult: mCurrentPhoto "+mCurrentPhoto.getAbsolutePath() + " " + mCurrentPhoto.exists() + " " + mCurrentPhoto.length());
327 if (mCurrentPhoto == null || !mCurrentPhoto.exists() || mCurrentPhoto.length() == 0) {
328 Toast.makeText(getActivity(), "Can't find picture", Toast.LENGTH_SHORT).show();
329 }
330 setLoading(true);
331 new Thread(() -> {
332 File file = mCurrentPhoto;
333 mCurrentPhoto = null;
334 presenter.sendFile(file);
335 getActivity().runOnUiThread(() -> setLoading(false));
336 }).start();
Pierre Duchemin17542a72018-02-14 16:38:36 -0500337 }
338 }
339
340 @Override
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500341 public void writeCacheFile(String cacheFilename) {
Adrien Béraud20d42212018-03-02 14:40:38 -0500342 // todo use rx + move to presenter
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500343 File cacheFile = new File(getActivity().getCacheDir(), cacheFilename);
344 try {
Adrien Béraud64538722018-03-15 17:34:00 -0400345 String finalFilePath = AndroidFileUtils.writeCacheFileToExtStorage(getActivity(), android.net.Uri.fromFile(cacheFile), cacheFile.getName());
Pierre Duchemine0c3a492018-02-01 16:22:45 -0500346
347 // Tell the media scanner about the new file so that it is immediately available to the user
348 MediaScannerConnection.scanFile(getActivity(), new String[]{finalFilePath}, null, null);
349
350 getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), getActivity().getString(R.string.file_saved_in, finalFilePath), Toast.LENGTH_LONG).show());
351 } catch (IOException e) {
352 Log.e(TAG, "writeCacheFile: ", e);
353 getActivity().runOnUiThread(() -> displayErrorToast(RingError.NOT_ABLE_TO_WRITE_FILE));
354 }
355 }
356
Adrien Béraud21533292018-03-22 14:11:35 -0400357 @Override
358 public void updateTransfer(DataTransfer transfer) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400359 mAdapter.updateTransfer(transfer);
360 }
361
362 @Override
363 public void addElement(ConversationElement element) {
364 mAdapter.add(element);
365 scrollToEnd();
366 }
367 @Override
368 public void updateElement(ConversationElement element) {
369 mAdapter.update(element);
370 }
371 @Override
372 public void removeElement(ConversationElement element) {
373 mAdapter.remove(element);
Adrien Béraud21533292018-03-22 14:11:35 -0400374 }
375
Adrien Béraudfcf457f2018-04-03 17:46:34 -0400376 @Override
377 public void shareFile(File path) {
378 android.net.Uri fileUri = null;
379 try {
380 fileUri = FileProvider.getUriForFile(
381 getActivity(),
382 ContentUriHandler.AUTHORITY_FILES,
383 path);
384 } catch (IllegalArgumentException e) {
385 Log.e("File Selector",
386 "The selected file can't be shared: " +
387 path.getName());
388 }
389 if (fileUri != null) {
390 Intent sendIntent = new Intent();
391 sendIntent.setAction(Intent.ACTION_SEND);
392 sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
393 String type = getActivity().getContentResolver().getType(fileUri);
394 sendIntent.setDataAndType(fileUri, type);
395 sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
396 startActivity(Intent.createChooser(sendIntent, null));
397 }
398 }
399
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500400 @OnEditorAction(R.id.msg_input_txt)
Alexandre Lision66a9f9a2017-02-26 20:28:27 -0500401 public boolean actionSendMsgText(int actionId) {
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500402 switch (actionId) {
403 case EditorInfo.IME_ACTION_SEND:
Adrien Béraudace37a22018-06-22 14:58:36 -0400404 sendMessageText();
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500405 return true;
406 }
407 return false;
408 }
409
410 @OnClick(R.id.ongoingcall_pane)
Alexandre Lision66a9f9a2017-02-26 20:28:27 -0500411 public void onClick() {
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400412 presenter.clickOnGoingPane();
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500413 }
414
415 @Override
416 public void onPause() {
417 super.onPause();
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400418 presenter.pause();
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500419 }
420
421 @Override
422 public void onResume() {
423 super.onResume();
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400424 presenter.resume();
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500425 }
426
427 @Override
428 public void onDestroy() {
429 if (mDeleteConversation) {
430 mDeleteDialog.dismiss();
431 }
432
433 super.onDestroy();
434 }
435
436 @Override
437 public void onSaveInstanceState(Bundle outState) {
438 super.onSaveInstanceState(outState);
439
440 // persist the delete popup state in case of Activity rotation
441 mDeleteConversation = mDeleteDialog != null && mDeleteDialog.isShowing();
442 outState.putBoolean(CONVERSATION_DELETE, mDeleteConversation);
443 }
444
445 @Override
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500446 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400447 inflater.inflate(R.menu.conversation_actions, menu);
Adrien Béraud5b47cb52018-07-06 16:24:14 -0400448 mAudioCallBtn = menu.findItem(R.id.conv_action_audiocall);
449 mVideoCallBtn = menu.findItem(R.id.conv_action_videocall);
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500450 }
451
452 @Override
453 public boolean onOptionsItemSelected(MenuItem item) {
454 switch (item.getItemId()) {
455 case android.R.id.home:
456 startActivity(new Intent(getActivity(), HomeActivity.class));
457 return true;
458 case R.id.conv_action_audiocall:
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500459 presenter.callWithAudioOnly(true);
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500460 return true;
461 case R.id.conv_action_videocall:
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500462 presenter.callWithAudioOnly(false);
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500463 return true;
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500464 case R.id.menuitem_delete:
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400465 presenter.deleteAction();
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500466 return true;
467 case R.id.menuitem_copy_content:
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400468 presenter.copyToClipboard();
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500469 return true;
Aline Bonnet54cf69b2017-03-28 12:03:45 -0400470 case R.id.menuitem_block:
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400471 presenter.blockContact();
Aline Bonnet54cf69b2017-03-28 12:03:45 -0400472 return true;
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500473 default:
474 return super.onOptionsItemSelected(item);
475 }
476 }
477
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500478 @Override
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500479 public void deleteConversation(CallContact callContact) {
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400480 presenter.deleteConversation();
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500481 }
482
483 @Override
484 public void copyContactNumberToClipboard(String contactNumber) {
485 ClipboardHelper.copyNumberToClipboard(getActivity(), contactNumber, this);
486 }
487
488 @Override
489 public void clipBoardDidCopyNumber(String copiedNumber) {
490 View view = getActivity().findViewById(android.R.id.content);
491 if (view != null) {
492 String snackbarText = getString(R.string.conversation_action_copied_peer_number_clipboard,
Alexandre Lision6fd56c82017-01-30 09:05:54 -0500493 ActionHelper.getShortenedNumber(copiedNumber));
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500494 Snackbar.make(view, snackbarText, Snackbar.LENGTH_LONG).show();
495 }
496 }
497
498 @Override
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400499 protected void initPresenter(ConversationPresenter presenter) {
500 super.initPresenter(presenter);
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500501 String contactRingID = getArguments().getString(KEY_CONTACT_RING_ID);
502 String accountId = getArguments().getString(KEY_ACCOUNT_ID);
Adrien Béraudace37a22018-06-22 14:58:36 -0400503 mAdapter = new ConversationAdapter(this, presenter);
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500504 presenter.init(contactRingID, accountId);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400505 }
506
507 @Override
Cyrille Béraud35b5a892017-07-22 17:20:13 -0400508 public void displayContactName(final CallContact contact) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400509 ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
510 if (actionBar == null) {
511 return;
512 }
513 String displayName = contact.getDisplayName();
514 actionBar.setTitle(displayName);
515 String identity = contact.getRingUsername();
516 if (identity != null && !identity.equals(displayName)) {
517 actionBar.setSubtitle(identity);
518 }
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400519 }
520
521 @Override
522 public void displayOnGoingCallPane(final boolean display) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400523 mTopPane.setVisibility(display ? View.VISIBLE : View.GONE);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400524 }
525
526 @Override
Alexandre Lision62c69782017-04-04 17:26:44 -0400527 public void displayContactPhoto(final byte[] photo) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400528 mAdapter.setPhoto(photo);
Alexandre Lision62c69782017-04-04 17:26:44 -0400529 }
530
531 @Override
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400532 public void displayNumberSpinner(final Conversation conversation, final Uri number) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400533 mNumberSpinner.setVisibility(View.VISIBLE);
534 mNumberAdapter = new NumberAdapter(getActivity(),
535 conversation.getContact(),
536 false);
537 mNumberSpinner.setAdapter(mNumberAdapter);
538 mNumberSpinner.setSelection(getIndex(mNumberSpinner, number));
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400539 }
540
541 @Override
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400542 public void displayDeleteDialog(final Conversation conversation) {
Adrien Béraudace37a22018-06-22 14:58:36 -0400543 mDeleteDialog = ActionHelper.launchDeleteAction(getActivity(),
Pierre Duchemin45707ca2018-01-15 15:05:23 -0500544 conversation.getContact(),
Adrien Béraudace37a22018-06-22 14:58:36 -0400545 ConversationFragment.this);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400546 }
547
548 @Override
549 public void displayCopyToClipboard(CallContact callContact) {
550 ActionHelper.launchCopyNumberToClipboardFromContact(getActivity(),
551 callContact,
552 this);
553 }
554
555 @Override
556 public void hideNumberSpinner() {
Adrien Béraudace37a22018-06-22 14:58:36 -0400557 mNumberSpinner.setVisibility(View.GONE);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400558 }
559
560 @Override
561 public void clearMsgEdit() {
562 mMsgEditTxt.setText("");
563 }
564
565 @Override
566 public void goToHome() {
567 if (getActivity() instanceof ConversationActivity) {
568 getActivity().finish();
Aline Bonneteb5f7ff2017-01-17 11:09:17 -0500569 }
570 }
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400571
572 @Override
573 public void goToAddContact(CallContact callContact) {
574 startActivityForResult(ActionHelper.getAddNumberIntentForContact(callContact), REQ_ADD_CONTACT);
575 }
576
577 @Override
578 public void goToCallActivity(String conferenceId) {
579 startActivity(new Intent(Intent.ACTION_VIEW)
580 .setClass(getActivity().getApplicationContext(), CallActivity.class)
Hadrien De Sousaccc947d2017-04-12 14:26:52 -0400581 .putExtra(NotificationService.KEY_CALL_ID, conferenceId));
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400582 }
583
584 @Override
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500585 public void goToCallActivityWithResult(String accountId, String contactRingId, boolean audioOnly) {
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400586 Intent intent = new Intent(CallActivity.ACTION_CALL)
587 .setClass(getActivity().getApplicationContext(), CallActivity.class)
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500588 .putExtra(KEY_ACCOUNT_ID, accountId)
Hadrien De Sousa203164c2017-12-05 17:41:42 -0500589 .putExtra(CallFragment.KEY_AUDIO_ONLY, audioOnly)
Hadrien De Sousa0dc83162017-11-10 16:15:18 -0500590 .putExtra(KEY_CONTACT_RING_ID, contactRingId);
Hadrien De Sousaec95e5e2017-03-21 15:28:23 -0400591 startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL);
592 }
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500593
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500594 @OnClick(R.id.btnBlock)
595 public void blockContactRequest() {
596 presenter.onBlockIncomingContactRequest();
597 }
598
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500599 @OnClick(R.id.btnRefuse)
600 public void refuseContactRequest() {
601 presenter.onRefuseIncomingContactRequest();
602 }
603
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500604 @OnClick(R.id.btnAccept)
605 public void acceptContactRequest() {
606 presenter.onAcceptIncomingContactRequest();
607 }
608
Adrien Béraudace37a22018-06-22 14:58:36 -0400609 @OnClick(R.id.btnAddContact)
610 public void addContact() {
611 presenter.onAddContact();
612 }
613
614 @Override
Adrien Béraud5b47cb52018-07-06 16:24:14 -0400615 public void onPrepareOptionsMenu(Menu menu) {
616 super.onPrepareOptionsMenu(menu);
617 boolean visible = mMessageInput.getVisibility() == View.VISIBLE;
618 if (mAudioCallBtn != null)
619 mAudioCallBtn.setVisible(visible);
620 if (mVideoCallBtn != null)
621 mVideoCallBtn.setVisible(visible);
622 }
623
624 @Override
Adrien Béraudace37a22018-06-22 14:58:36 -0400625 public void switchToUnknownView(String contactDisplayName) {
626 mMessageInput.setVisibility(View.GONE);
627 mUnknownPrompt.setVisibility(View.VISIBLE);
628 mTrustRequestPrompt.setVisibility(View.GONE);
629 mTvTrustRequestMessage.setText(String.format(getString(R.string.message_contact_not_trusted), contactDisplayName));
630 mTrustRequestMessageLayout.setVisibility(View.VISIBLE);
Adrien Béraud5b47cb52018-07-06 16:24:14 -0400631 getActivity().invalidateOptionsMenu();
Adrien Béraudace37a22018-06-22 14:58:36 -0400632 }
633
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500634 @Override
635 public void switchToIncomingTrustRequestView(String contactDisplayName) {
636 mMessageInput.setVisibility(View.GONE);
Adrien Béraudace37a22018-06-22 14:58:36 -0400637 mUnknownPrompt.setVisibility(View.GONE);
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500638 mTrustRequestPrompt.setVisibility(View.VISIBLE);
Adrien Béraudace37a22018-06-22 14:58:36 -0400639 mTvTrustRequestMessage.setText(String.format(getString(R.string.message_contact_not_trusted_yet), contactDisplayName));
640 mTrustRequestMessageLayout.setVisibility(View.VISIBLE);
Adrien Béraud5b47cb52018-07-06 16:24:14 -0400641 getActivity().invalidateOptionsMenu();
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500642 }
643
644 @Override
645 public void switchToConversationView() {
646 mMessageInput.setVisibility(View.VISIBLE);
Adrien Béraudace37a22018-06-22 14:58:36 -0400647 mUnknownPrompt.setVisibility(View.GONE);
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500648 mTrustRequestPrompt.setVisibility(View.GONE);
Adrien Béraudace37a22018-06-22 14:58:36 -0400649 mTrustRequestMessageLayout.setVisibility(View.GONE);
Adrien Béraud5b47cb52018-07-06 16:24:14 -0400650 getActivity().invalidateOptionsMenu();
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500651 }
652
653 @Override
654 public void positiveMediaButtonClicked() {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500655 presenter.clickOnGoingPane();
656 }
657
658 @Override
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500659 public void negativeMediaButtonClicked() {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500660 presenter.clickOnGoingPane();
661 }
662
663 @Override
Pierre Duchemin1fa61922018-01-18 10:09:02 -0500664 public void toggleMediaButtonClicked() {
Pierre Duchemin8b2c1b52017-12-29 17:17:13 -0500665 presenter.clickOnGoingPane();
666 }
Pierre Duchemin17542a72018-02-14 16:38:36 -0500667
668 private void setLoading(boolean isLoading) {
Adrien Béraud5b47cb52018-07-06 16:24:14 -0400669 if (takePicture == null || sendData == null || pbDataTransfer == null)
670 return;
Pierre Duchemin17542a72018-02-14 16:38:36 -0500671 if (isLoading) {
Adrien Béraud64538722018-03-15 17:34:00 -0400672 takePicture.setVisibility(View.GONE);
Pierre Duchemin17542a72018-02-14 16:38:36 -0500673 sendData.setVisibility(View.GONE);
674 pbDataTransfer.setVisibility(View.VISIBLE);
675 } else {
Adrien Béraud64538722018-03-15 17:34:00 -0400676 takePicture.setVisibility(View.VISIBLE);
Pierre Duchemin17542a72018-02-14 16:38:36 -0500677 sendData.setVisibility(View.VISIBLE);
678 pbDataTransfer.setVisibility(View.GONE);
679 }
680 }
Adrien Béraud1b8ac9f2018-05-31 14:34:23 -0400681
682 @Override
683 public void displayCompletedDownload(DataTransfer transfer, File destination) {
684 DownloadManager downloadManager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
685 if (downloadManager != null) {
686 downloadManager.addCompletedDownload(transfer.getDisplayName(),
687 transfer.getDisplayName(),
688 true,
689 transfer.isPicture() ? "image/jpeg" : "text/plain",
690 destination.getAbsolutePath(),
691 destination.length(),
692 true);
693 }
694 }
Adrien Béraudae45ffa2018-07-06 16:21:29 -0400695
696 public void handleShareIntent(Intent intent) {
697 String type = intent.getType();
698 if (type == null) {
699 Log.w(TAG, "Can't share with no type");
700 return;
701 }
702 if (type.startsWith("text/")) {
703 mMsgEditTxt.setText(intent.getStringExtra(Intent.EXTRA_TEXT));
704 } else if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("application/")) {
705 android.net.Uri uri = intent.getData();
706 ClipData clip = intent.getClipData();
707 if (uri == null && clip != null && clip.getItemCount() > 0)
708 uri = clip.getItemAt(0).getUri();
709 if (uri == null)
710 return;
711 final android.net.Uri shareUri = uri;
712 setLoading(true);
713 new Thread(() -> {
714 try {
715 File cacheFile = AndroidFileUtils.getCacheFile(getActivity(), shareUri);
716 presenter.sendFile(cacheFile);
717 } catch (IOException e) {
718 Log.e(TAG, "onActivityResult: not able to create cache file");
719 getActivity().runOnUiThread(() -> displayErrorToast(RingError.INVALID_FILE));
720 }
721 getActivity().runOnUiThread(() -> setLoading(false));
722 }).start();
723 }
724 }
Aline Bonnet9c6defe2016-12-21 13:21:02 -0500725}