net.kourlas.voipms_sms.activities.ConversationActivity.java Source code

Java tutorial

Introduction

Here is the source code for net.kourlas.voipms_sms.activities.ConversationActivity.java

Source

/*
 * VoIP.ms SMS
 * Copyright (C) 2015 Michael Kourlas and other contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.kourlas.voipms_sms.activities;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.*;
import android.content.pm.PackageManager;
import android.graphics.Outline;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.*;
import android.widget.*;
import net.kourlas.voipms_sms.*;
import net.kourlas.voipms_sms.adapters.ConversationRecyclerViewAdapter;
import net.kourlas.voipms_sms.model.Conversation;
import net.kourlas.voipms_sms.model.Message;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class ConversationActivity extends AppCompatActivity
        implements ActionMode.Callback, View.OnLongClickListener, View.OnClickListener {
    public static final String TAG = "ConversationActivity";

    private final ConversationActivity activity = this;

    private String contact;

    private Menu menu;

    private RecyclerView recyclerView;
    private LinearLayoutManager layoutManager;
    private ConversationRecyclerViewAdapter adapter;

    private ActionMode actionMode;
    private boolean actionModeEnabled;

    private Database database;
    private Preferences preferences;

    public static void sendMessage(Activity sourceActivity, long databaseId) {
        Context applicationContext = sourceActivity.getApplicationContext();
        Database database = Database.getInstance(applicationContext);
        Preferences preferences = Preferences.getInstance(applicationContext);

        Message message = database.getMessageWithDatabaseId(preferences.getDid(), databaseId);
        SendMessageTask task = new SendMessageTask(sourceActivity.getApplicationContext(), message, sourceActivity);

        if (preferences.getEmail().equals("") || preferences.getPassword().equals("")
                || preferences.getDid().equals("")) {
            // Do not show an error; this method should never be called unless the email, password and DID are set
            task.cleanup(false);
            return;
        }

        if (!Utils.isNetworkConnectionAvailable(applicationContext)) {
            Toast.makeText(applicationContext,
                    applicationContext.getString(R.string.conversation_send_error_network), Toast.LENGTH_SHORT)
                    .show();
            task.cleanup(false);
            return;
        }

        try {
            String voipUrl = "https://www.voip.ms/api/v1/rest.php?" + "api_username="
                    + URLEncoder.encode(preferences.getEmail(), "UTF-8") + "&" + "api_password="
                    + URLEncoder.encode(preferences.getPassword(), "UTF-8") + "&" + "method=sendSMS" + "&" + "did="
                    + URLEncoder.encode(preferences.getDid(), "UTF-8") + "&" + "dst="
                    + URLEncoder.encode(message.getContact(), "UTF-8") + "&" + "message="
                    + URLEncoder.encode(message.getText(), "UTF-8");
            task.start(voipUrl);
        } catch (UnsupportedEncodingException ex) {
            // This should never happen since the encoding (UTF-8) is hardcoded
            throw new Error(ex);
        }
    }

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.conversation);

        database = Database.getInstance(getApplicationContext());
        preferences = Preferences.getInstance(getApplicationContext());

        contact = getIntent().getStringExtra(getString(R.string.conversation_extra_contact));
        // Remove the leading one from a North American phone number (e.g. +1 (123) 555-4567)
        if ((contact.length() == 11) && (contact.charAt(0) == '1')) {
            contact = contact.substring(1);
        }

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        ViewCompat.setElevation(toolbar, getResources().getDimension(R.dimen.toolbar_elevation));
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            String contactName = Utils.getContactName(this, contact);
            if (contactName != null) {
                actionBar.setTitle(contactName);
            } else {
                actionBar.setTitle(Utils.getFormattedPhoneNumber(contact));
            }
            actionBar.setHomeButtonEnabled(true);
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        layoutManager.setStackFromEnd(true);
        adapter = new ConversationRecyclerViewAdapter(this, layoutManager, contact);
        recyclerView = (RecyclerView) findViewById(R.id.list);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(adapter);

        actionMode = null;
        actionModeEnabled = false;

        final EditText messageText = (EditText) findViewById(R.id.message_edit_text);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            messageText.setOutlineProvider(new ViewOutlineProvider() {
                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                @Override
                public void getOutline(View view, Outline outline) {
                    outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), 15);
                }
            });
            messageText.setClipToOutline(true);
        }
        messageText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // Do nothing.
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // Do nothing.
            }

            @Override
            public void afterTextChanged(Editable s) {
                ViewSwitcher viewSwitcher = (ViewSwitcher) findViewById(R.id.view_switcher);
                if (s.toString().equals("") && viewSwitcher.getDisplayedChild() == 1) {
                    viewSwitcher.setDisplayedChild(0);
                } else if (viewSwitcher.getDisplayedChild() == 0) {
                    viewSwitcher.setDisplayedChild(1);
                }
            }
        });
        messageText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    adapter.refresh();
                }
            }
        });
        String intentMessageText = getIntent().getStringExtra(getString(R.string.conversation_extra_message_text));
        if (intentMessageText != null) {
            messageText.setText(intentMessageText);
        }
        boolean intentFocus = getIntent().getBooleanExtra(getString(R.string.conversation_extra_focus), false);
        if (intentFocus) {
            messageText.requestFocus();
        }

        RelativeLayout messageSection = (RelativeLayout) findViewById(R.id.message_section);
        ViewCompat.setElevation(messageSection, 8);

        QuickContactBadge photo = (QuickContactBadge) findViewById(R.id.photo);
        Utils.applyCircularMask(photo);
        photo.assignContactFromPhone(preferences.getDid(), true);
        String photoUri = Utils.getContactPhotoUri(getApplicationContext(), preferences.getDid());
        if (photoUri != null) {
            photo.setImageURI(Uri.parse(photoUri));
        } else {
            photo.setImageToDefault();
        }

        final ImageButton sendButton = (ImageButton) findViewById(R.id.send_button);
        Utils.applyCircularMask(sendButton);
        sendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                preSendMessage();
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        ActivityMonitor.getInstance().setCurrentActivity(this);

        Integer id = Notifications.getInstance(getApplicationContext()).getNotificationIds().get(contact);
        if (id != null) {
            NotificationManager notificationManager = (NotificationManager) getApplicationContext()
                    .getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.cancel(id);
        }

        markConversationAsRead();

        adapter.refresh();
    }

    @Override
    protected void onPause() {
        super.onPause();
        ActivityMonitor.getInstance().deleteReferenceToActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityMonitor.getInstance().deleteReferenceToActivity(this);
    }

    @Override
    public void onBackPressed() {
        if (actionModeEnabled) {
            actionMode.finish();
        } else if (menu != null) {
            MenuItem searchItem = menu.findItem(R.id.search_button);
            SearchView searchView = (SearchView) searchItem.getActionView();
            if (!searchView.isIconified()) {
                searchItem.collapseActionView();
            } else {
                super.onBackPressed();
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(final Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.conversation, menu);
        this.menu = menu;

        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
            MenuItem phoneMenuItem = menu.findItem(R.id.call_button);
            phoneMenuItem.setVisible(false);
        }

        SearchView searchView = (SearchView) menu.findItem(R.id.search_button).getActionView();
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                adapter.refresh(newText);
                return true;
            }
        });

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            switch (item.getItemId()) {
            case R.id.call_button:
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:" + contact));
                startActivity(intent);
                return true;
            case R.id.delete_button:
                Conversation conversation = database.getConversation(preferences.getDid(), contact);
                if (conversation.getMessages().length == 0) {
                    NavUtils.navigateUpFromSameTask(this);
                } else {
                    List<Long> databaseIds = new ArrayList<>();
                    for (int i = 0; i < adapter.getItemCount(); i++) {
                        if (adapter.getItem(i).getDatabaseId() != null) {
                            databaseIds.add(adapter.getItem(i).getDatabaseId());
                        }
                    }

                    Long[] databaseIdsArray = new Long[databaseIds.size()];
                    databaseIds.toArray(databaseIdsArray);
                    deleteMessages(databaseIdsArray);
                }
                return true;
            }
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
        MenuInflater inflater = actionMode.getMenuInflater();
        inflater.inflate(R.menu.conversation_secondary, menu);
        return true;
    }

    @Override
    public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
        return false;
    }

    @Override
    public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
        if (menuItem.getItemId() == R.id.resend_button) {
            for (int i = 0; i < adapter.getItemCount(); i++) {
                if (adapter.isItemChecked(i)) {
                    sendMessage(adapter.getItem(i).getDatabaseId());
                    break;
                }
            }

            actionMode.finish();
            return true;
        } else if (menuItem.getItemId() == R.id.info_button) {
            Message message = null;
            for (int i = 0; i < adapter.getItemCount(); i++) {
                if (adapter.isItemChecked(i)) {
                    message = adapter.getItem(i);
                    break;
                }
            }

            if (message != null) {
                DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
                String dialogText;
                if (message.getType() == Message.Type.INCOMING) {
                    dialogText = (message.getVoipId() == null ? ""
                            : (getString(R.string.conversation_info_id) + " " + message.getVoipId() + "\n"))
                            + getString(R.string.conversation_info_to) + " "
                            + Utils.getFormattedPhoneNumber(message.getDid()) + "\n"
                            + getString(R.string.conversation_info_from) + " "
                            + Utils.getFormattedPhoneNumber(message.getContact()) + "\n"
                            + getString(R.string.conversation_info_date) + " "
                            + dateFormat.format(message.getDate());
                } else {
                    dialogText = (message.getVoipId() == null ? ""
                            : (getString(R.string.conversation_info_id) + " " + message.getVoipId() + "\n"))
                            + getString(R.string.conversation_info_to) + " "
                            + Utils.getFormattedPhoneNumber(message.getContact()) + "\n"
                            + getString(R.string.conversation_info_from) + " "
                            + Utils.getFormattedPhoneNumber(message.getDid()) + "\n"
                            + getString(R.string.conversation_info_date) + " "
                            + dateFormat.format(message.getDate());
                }
                Utils.showAlertDialog(this, getString(R.string.conversation_info_title), dialogText,
                        getString(R.string.ok), null, null, null);
            }

            actionMode.finish();
            return true;
        } else if (menuItem.getItemId() == R.id.copy_button) {
            Message message = null;
            for (int i = 0; i < adapter.getItemCount(); i++) {
                if (adapter.isItemChecked(i)) {
                    message = adapter.getItem(i);
                    break;
                }
            }

            if (message != null) {
                ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                ClipData clip = ClipData.newPlainText("Text message", message.getText());
                clipboard.setPrimaryClip(clip);
            }

            actionMode.finish();
            return true;
        } else if (menuItem.getItemId() == R.id.share_button) {
            Message message = null;
            for (int i = 0; i < adapter.getItemCount(); i++) {
                if (adapter.isItemChecked(i)) {
                    message = adapter.getItem(i);
                    break;
                }
            }

            if (message != null) {
                Intent intent = new Intent(android.content.Intent.ACTION_SEND);
                intent.setType("text/plain");
                intent.putExtra(android.content.Intent.EXTRA_TEXT, message.getText());
                startActivity(Intent.createChooser(intent, null));
            }

            actionMode.finish();
            return true;
        } else if (menuItem.getItemId() == R.id.delete_button) {
            List<Long> databaseIds = new ArrayList<>();
            for (int i = 0; i < adapter.getItemCount(); i++) {
                if (adapter.isItemChecked(i)) {
                    databaseIds.add(adapter.getItem(i).getDatabaseId());
                }
            }

            Long[] databaseIdsArray = new Long[databaseIds.size()];
            databaseIds.toArray(databaseIdsArray);
            deleteMessages(databaseIdsArray);

            actionMode.finish();
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void onDestroyActionMode(ActionMode actionMode) {
        for (int i = 0; i < adapter.getItemCount(); i++) {
            adapter.setItemChecked(i, false);
        }
        actionModeEnabled = false;
    }

    private void updateButtons() {
        MenuItem resendAction = actionMode.getMenu().findItem(R.id.resend_button);
        MenuItem copyAction = actionMode.getMenu().findItem(R.id.copy_button);
        MenuItem shareAction = actionMode.getMenu().findItem(R.id.share_button);
        MenuItem infoAction = actionMode.getMenu().findItem(R.id.info_button);

        int count = adapter.getCheckedItemCount();

        boolean resendVisible = false;
        if (count == 1) {
            for (int i = 0; i < adapter.getItemCount(); i++) {
                if (adapter.isItemChecked(i)) {
                    if (!adapter.getItem(i).isDelivered() && !adapter.getItem(i).isDeliveryInProgress()) {
                        resendVisible = true;
                        break;
                    }
                }
            }
        }
        resendAction.setVisible(resendVisible);

        if (count >= 2) {
            infoAction.setVisible(false);
            copyAction.setVisible(false);
            shareAction.setVisible(false);
        } else {
            infoAction.setVisible(true);
            copyAction.setVisible(true);
            shareAction.setVisible(true);
        }
    }

    @Override
    public void onClick(View view) {
        if (actionModeEnabled) {
            toggleItem(view);
        } else {
            Message message = adapter.getItem(recyclerView.getChildAdapterPosition(view));
            if (!message.isDelivered() && !message.isDeliveryInProgress()) {
                preSendMessage(message.getDatabaseId());
            }
        }
    }

    @Override
    public boolean onLongClick(View view) {
        toggleItem(view);
        return true;
    }

    private void toggleItem(View view) {
        adapter.toggleItemChecked(recyclerView.getChildAdapterPosition(view));

        if (adapter.getCheckedItemCount() == 0) {
            if (actionMode != null) {
                actionMode.finish();
            }
            actionModeEnabled = false;
            return;
        }

        if (!actionModeEnabled) {
            actionMode = startSupportActionMode(this);
            actionModeEnabled = true;
        }
        updateButtons();
    }

    public void deleteMessages(final Long[] databaseIds) {
        Utils.showAlertDialog(this, getString(R.string.conversation_delete_confirm_title),
                getString(R.string.conversation_delete_confirm_message), getString(R.string.delete),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        for (long databaseId : databaseIds) {
                            Message message = database.getMessageWithDatabaseId(preferences.getDid(), databaseId);
                            if (message.getVoipId() == null) {
                                database.removeMessage(databaseId);
                            } else {
                                message.setDeleted(true);
                                database.insertMessage(message);
                            }
                            adapter.refresh();
                        }
                    }
                }, getString(R.string.cancel), null);
    }

    public void preSendMessage() {
        EditText messageEditText = (EditText) findViewById(R.id.message_edit_text);
        String messageText = messageEditText.getText().toString();
        while (true) {
            if (messageText.length() > 160) {
                long databaseId = database
                        .insertMessage(new Message(preferences.getDid(), contact, messageText.substring(0, 160)));
                messageText = messageText.substring(160);
                adapter.refresh();
                preSendMessage(databaseId);
            } else {
                long databaseId = database.insertMessage(
                        new Message(preferences.getDid(), contact, messageText.substring(0, messageText.length())));
                adapter.refresh();
                preSendMessage(databaseId);
                break;
            }
        }
        messageEditText.setText("");
    }

    public void preSendMessage(long databaseId) {
        Message message = database.getMessageWithDatabaseId(preferences.getDid(), databaseId);
        message.setDelivered(false);
        message.setDeliveryInProgress(true);
        database.insertMessage(message);
        adapter.refresh();

        sendMessage(databaseId);
    }

    public void postSendMessage(boolean success, long databaseId) {
        if (success) {
            database.removeMessage(databaseId);
            database.synchronize(true, true, this);
        } else {
            Message message = database.getMessageWithDatabaseId(preferences.getDid(), databaseId);
            message.setDelivered(false);
            message.setDeliveryInProgress(false);
            database.insertMessage(message);
            adapter.refresh();
        }

        if (adapter.getItemCount() > 0) {
            layoutManager.scrollToPosition(adapter.getItemCount() - 1);
        }
    }

    public void postUpdate() {
        markConversationAsRead();
        adapter.refresh();
    }

    public String getContact() {
        return contact;
    }

    public void markConversationAsRead() {
        for (Message message : database.getConversation(preferences.getDid(), contact).getMessages()) {
            message.setUnread(false);
            database.insertMessage(message);
        }
    }

    public void sendMessage(long databaseId) {
        sendMessage(this, databaseId);
    }

    public static class SendMessageTask {
        private final Context applicationContext;

        private final Message message;
        private final Activity sourceActivity;

        public SendMessageTask(Context applicationContext, Message message, Activity sourceActivity) {
            this.applicationContext = applicationContext;

            this.message = message;
            this.sourceActivity = sourceActivity;
        }

        public void start(String voipUrl) {
            new SendMessageAsyncTask().execute(voipUrl);
        }

        public void cleanup(boolean success) {
            if (sourceActivity instanceof ConversationActivity) {
                ((ConversationActivity) sourceActivity).postSendMessage(success, message.getDatabaseId());
            } else if (sourceActivity instanceof ConversationQuickReplyActivity) {
                ((ConversationQuickReplyActivity) sourceActivity).postSendMessage(success, message.getDatabaseId());
            }
        }

        private class SendMessageAsyncTask extends AsyncTask<String, String, Boolean> {
            @Override
            protected Boolean doInBackground(String... params) {
                JSONObject resultJson;
                try {
                    resultJson = Utils.getJson(params[0]);
                } catch (JSONException ex) {
                    Log.w(TAG, Log.getStackTraceString(ex));
                    publishProgress(applicationContext.getString(R.string.conversation_send_error_api_parse));
                    return false;
                } catch (Exception ex) {
                    Log.w(TAG, Log.getStackTraceString(ex));
                    publishProgress(applicationContext.getString(R.string.conversation_send_error_api_request));
                    return false;
                }

                String status = resultJson.optString("status");
                if (status == null) {
                    publishProgress(applicationContext.getString(R.string.conversation_send_error_api_parse));
                    return false;
                }
                if (!status.equals("success")) {
                    publishProgress(applicationContext.getString(R.string.conversation_send_error_api_error)
                            .replace("{error}", status));
                    return false;
                }

                return true;
            }

            @Override
            protected void onPostExecute(Boolean success) {
                cleanup(success);
            }

            /**
             * Shows a toast to the user.
             *
             * @param message The message to show. This must be a String array with a single element containing the
             *                message.
             */
            @Override
            protected void onProgressUpdate(String... message) {
                Toast.makeText(applicationContext, message[0], Toast.LENGTH_SHORT).show();
            }
        }
    }
}