package com.steps.geosms.conversation; import android.app.LoaderManager; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ContentValues; import android.content.Context; import android.content.CursorLoader; import android.content.Intent; import android.content.IntentFilter; import android.content.Loader; import android.content.SharedPreferences; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.util.SparseBooleanArray; import android.view.ActionMode; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.view.inputmethod.InputMethodManager; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import android.widget.ToggleButton; import com.ioane.sharvadze.geosms.R; import com.steps.geosms.GeoSmsManager; import com.steps.geosms.MyPreferencesManager; import com.steps.geosms.notifications.SmsManagerService; import com.steps.geosms.objects.Contact; import com.steps.geosms.objects.Conversation; import com.steps.geosms.objects.SMS; import com.steps.geosms.utils.Constants; import com.steps.geosms.utils.MyActivity; import com.steps.geosms.utils.Utils; import java.util.ArrayList; import java.util.Date; import java.util.List; public class ConversationActivity extends MyActivity implements LoaderManager.LoaderCallbacks<Cursor> ,TextWatcher{ private static final String TAG = ConversationActivity.class.getSimpleName(); private ConversationCursorAdapter adapter; private ToggleButton webUseToggle; /** This is our conversation id, this defines in which conversation we are.*/ private static long thread_id; /** Recipients of the conversation */ private ArrayList<Contact> contacts; private static boolean isKeyboardVisible = false; private GeoSmsManager smsManager; private BroadcastReceiver mNetworkChangeListener; /** If user changed toggle, we set as true */ private boolean userChangedWebToggle = false; private ImageButton button; private TextView symbolCounter; private ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_conversation); smsManager = new GeoSmsManager(getBaseContext()); webUseToggle = (ToggleButton)findViewById(R.id.use_web_toggle_button); button = (ImageButton)findViewById(R.id.send_button); button.setEnabled(false); final EditText editText = (EditText)findViewById(R.id.enter_message_edit_text); symbolCounter = (TextView)findViewById(R.id.symbol_counter); editText.addTextChangedListener(this); initConversation(); // if user entered here without contact we disable // everything. if(contacts == null){ editText.setEnabled(false); editText.setFocusable(false); return; } button.setOnClickListener(new SendButtonListener()); setTitle(Utils.getChatHeader(contacts)); adapter = new ConversationCursorAdapter(getBaseContext(),null,true,contacts.get(0)); listView = (ListView)findViewById(R.id.conversation_list_view); listView.setAdapter(adapter); listView.setOnTouchListener(new View.OnTouchListener() { InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); @Override public boolean onTouch(View v, MotionEvent event) { if(isKeyboardVisible) imm.hideSoftInputFromWindow(editText.getWindowToken(),0); return false; } }); if(!Utils.isDefaultSmsApp(this)){ editText.setFocusable(false); editText.setHint(R.string.set_default_app_to_send); webUseToggle.setEnabled(false); button.setEnabled(false); }else { SharedPreferences prefs = getSharedPreferences(Constants.DRAFTS_FILE,MODE_PRIVATE); String text = prefs.getString(Long.toString(thread_id),null); if(!TextUtils.isEmpty(text)) { button.setEnabled(true); editText.setText(text); } boolean isChecked = prefs.getBoolean(Constants.TOGGLE_CHECKED,false); webUseToggle.setChecked(isChecked); webUseToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { userChangedWebToggle = !userChangedWebToggle; } }); } mNetworkChangeListener = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE ); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); if(isConnected){ webUseToggle.setEnabled(true); }else webUseToggle.setEnabled(false); } }; initCab(); initKeyboardListener(); startLoader(); } /* * Initializes context action bar. */ private void initCab(){ listView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { mode.setTitle(String.format("%d", listView.getCheckedItemCount())); mode.invalidate(); } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.conversation_cab, menu); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { if (listView.getCheckedItemCount() == 1) { // single choice MenuItem item = menu.findItem(R.id.action_details); item.setVisible(true); return true; } else { // multi choice. MenuItem item = menu.findItem(R.id.action_details); item.setVisible(false); return true; } } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions(); final List<SMS> smsList = new ArrayList<>(checkedItemPositions.size()); Log.i(TAG, "eeh " + checkedItemPositions.size()); for (int i = 0; i < checkedItemPositions.size(); i++) { int pos = checkedItemPositions.keyAt(i); if (checkedItemPositions.get(pos)) { SMS sms = new SMS((Cursor) adapter.getItem(pos)); smsList.add(sms); } else { Log.i(TAG, "WTF"); } } if (smsList.isEmpty()) return false; switch (item.getItemId()) { case R.id.action_copy: ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); StringBuilder strBuilder = new StringBuilder(); for (SMS sms : smsList) { strBuilder.append(sms.getText()); } ClipData clip = ClipData.newPlainText("sms", strBuilder.toString()); clipboardManager.setPrimaryClip(clip); break; case R.id.action_send_as_new: for (SMS sms : smsList) { sms.setDate(new Date()); sendMessage(sms); } break; case R.id.action_details: AlertDialog.Builder alertBuilder = new AlertDialog.Builder(ConversationActivity.this); alertBuilder.setTitle(R.string.info) .setMessage(smsList.get(0).textReference()); alertBuilder.setCancelable(true); alertBuilder.show(); break; case R.id.action_delete: new Thread(new Runnable() { @Override public void run() { Utils.deleteSmsList(getBaseContext(), smsList); } }).start(); break; default: return false; } mode.finish(); return true; } @Override public void onDestroyActionMode(ActionMode mode) { } }); } /** * This method inits loader when thread_id is resolved. */ private void startLoader(){ // if we needn't thread_id resolve we don't create new thread. if(thread_id != Constants.THREAD_NONE){ SmsManagerService.updateThreadId(thread_id); getLoaderManager().initLoader(0, null, this); return; } new AsyncTask<Void,Void,Void>(){ @Override protected Void doInBackground(Void... params) { // this part resolves thread id. thread_id = Conversation.getOrCreateThreadId(getBaseContext(), Contact.toAddressArray(contacts)); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); SmsManagerService.updateThreadId(thread_id); getLoaderManager().initLoader(0, null, ConversationActivity.this); } }.execute(); } @Override protected void onResume() { super.onResume(); if(mNetworkChangeListener != null) registerReceiver(mNetworkChangeListener, new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")); if(!MyPreferencesManager.isWebSmsEnabled(getBaseContext())){ webUseToggle.setVisibility(View.GONE); supportInvalidateOptionsMenu(); }else { webUseToggle.setVisibility(View.VISIBLE); } if(contacts == null){ Log.w(TAG, "contact is null"); return; } SmsManagerService.updateThreadId(thread_id); new Thread(new Runnable() { @Override public void run() { SmsManagerService.dismissThreadNotifications(ConversationActivity.this,thread_id); markConversationAsRead(); } }).start(); } @Override protected void onPause() { super.onPause(); if(mNetworkChangeListener != null) unregisterReceiver(mNetworkChangeListener); SmsManagerService.updateThreadId(SmsManagerService.THREAD_ID_NONE); } public void resend(View view){ final SMS sms = (SMS)view.getTag(); if(sms == null) return; new Thread(new Runnable() { @Override public void run() { getContentResolver().delete(Constants.URIS.SMS, "_id=?", new String[]{"" + sms.getId()}); } }).start(); sendMessage(sms); } /** * This method initializes conversation. * * This means it resolves in which thread we are. * Which contacts are recipients. */ @SuppressWarnings("unchecked cast") private void initConversation(){ try{ Bundle extras = getIntent().getExtras(); if(extras != null){ contacts = (ArrayList<Contact>)extras.getSerializable(Constants.CONTACT_DATA); if(contacts != null) for(Contact c: contacts) c.resolveContactImage(getBaseContext(),80); Long passed_tid = extras.getLong(Constants.THREAD_ID, -1); if(passed_tid == -1 && contacts != null){ thread_id = Conversation.getOrCreateThreadId(getBaseContext(), Contact.toAddressArray(contacts)); }else thread_id = passed_tid; }else{ Uri uri = getIntent().getData(); if(uri == null) { Log.w(TAG,"Contact data not provided!"); return; } String scheme = uri.getScheme(); String schemePart = uri.getSchemeSpecificPart(); if(scheme == null || schemePart == null) return; if(!scheme.contains("sms") && !scheme.contains("smsto")) return; contacts = new ArrayList<>(1); Contact contact = new Contact(getBaseContext(),schemePart); contact.resolveContactImage(getBaseContext(),80); contacts.add(contact); thread_id = Conversation.getOrCreateThreadId(getBaseContext(), Contact.toAddressArray(contacts)); } if(contacts == null) return; for(Contact c : contacts){ if(c.getPhotoUri() != null) c.resolveContactImage(getBaseContext(),50); } }catch (Exception e){ e.printStackTrace(); } } private void markConversationAsRead(){ if(thread_id == Constants.THREAD_NONE) return; new AsyncTask<Long,Void,Void>(){ @Override protected Void doInBackground(Long... params) { long threadId = params[0]; ContentValues cv = new ContentValues(); cv.put(Constants.MESSAGE.READ,1); getContentResolver().update(Uri.parse("content://sms"),cv , ("thread_id = " + threadId), null); return null; } }.execute(thread_id); } @Override protected void onStop() { super.onStop(); if(contacts == null) return; EditText editText = (EditText)findViewById(R.id.enter_message_edit_text); if(!editText.isFocusable()) return; // not save when it's not default SharedPreferences drafts = getSharedPreferences(Constants.DRAFTS_FILE,MODE_PRIVATE); SharedPreferences.Editor editor = drafts.edit(); /* if user changed web use toggle , let's save it , to not make user choose on every conversation start...*/ if(userChangedWebToggle){ editor.putBoolean(Constants.TOGGLE_CHECKED,webUseToggle.isChecked()); } if(editText.getText() == null || editText.getText().toString().equals("")){ editor.remove(Long.toString(thread_id)); }else { editor.putString(Long.toString(thread_id), editText.getText().toString()); } editor.apply(); } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Uri uri = Uri.parse("content://sms/"); return new CursorLoader(getBaseContext(),uri,null,"thread_id = ?" , new String[]{Long.toString(thread_id)},"date asc"); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { adapter.swapCursor(data); adapter.notifyDataSetChanged(); } @Override public void onLoaderReset(Loader<Cursor> loader) { adapter.swapCursor(null); } private boolean isSendWeb(){ return webUseToggle.isChecked() && webUseToggle.getVisibility() == View.VISIBLE && webUseToggle.isEnabled(); } private void sendMessage(SMS sms){ for(String address : Contact.toAddressArray(contacts)) smsManager.sendSms(sms, address ,thread_id, isSendWeb()); } /** * * @Override public void onClick(View v) { if(v == null) return; final ViewHolder holder = (ViewHolder)v.getTag(); if(holder == null) return; AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext()); builder.setPositiveButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) {} }) .setNegativeButton(R.string.resend, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { SMS sms = new SMS((Cursor) getItem(holder.pos)); } }) .setTitle(R.string.resend_request) .setMessage(R.string.resend_request_body); } */ /** * Class for managing clicked send button. */ private class SendButtonListener implements View.OnClickListener{ @Override public void onClick(View view) { EditText editText = (EditText)findViewById(R.id.enter_message_edit_text); if(TextUtils.isEmpty(editText.getText())) return; String message = editText.getText().toString(); // it's a draft if(contacts == null){ SMS sms = new SMS(message,new Date(System.currentTimeMillis()), SMS.MsgType.DRAFT,true,false,null); smsManager.saveDraft(sms); }else{ SMS sms = new SMS(message,new Date(System.currentTimeMillis()), SMS.MsgType.PENDING,true,false,null); sendMessage(sms); } if (editText.length() > 0) { editText.setText(null); } } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if(listView != null){ int pos = listView.getFirstVisiblePosition(); outState.putInt("scroll_pos",pos); } } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); int pos = savedInstanceState.getInt("scroll_pos"); Log.i(TAG,"THIS SHIT IS = " + pos); if(pos != 0) listView.smoothScrollToPositionFromTop(pos,0,0); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } private static final int MESSAGE_DEFAULT_SIZE = 160; private static final int MESSAGE_UNICODE_SIZE = 70; /** * StackOverflow community answer. */ private void initKeyboardListener(){ final View activityRootView = findViewById(R.id.conversation_activity); activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { double ratio = ((double)activityRootView.getHeight())/activityRootView.getRootView().getHeight(); // if more than 100 pixels, its probably a keyboard... isKeyboardVisible = ratio < 0.75; } }); } boolean isASCII(String str){ for(int i=0;i<str.length();i++){ if(str.charAt(i) >= 128) return false; } return true; } @Override public void afterTextChanged(Editable s) { if (s == null || s.length() == 0) { button.setEnabled(false); }else button.setEnabled(true); if(symbolCounter == null) return; if(s == null){ symbolCounter.setText(""); return; } int maxLen = MESSAGE_DEFAULT_SIZE; if(!isASCII(s.toString())) maxLen = MESSAGE_UNICODE_SIZE; if(s.length() > maxLen/2){ symbolCounter.setVisibility(View.VISIBLE); String str; if(s.length()/maxLen == 0){ str = String.format("%d/%d",s.length()%maxLen,maxLen); }else{ str = String.format("%d/%d (%d)",s.length()%maxLen,maxLen,s.length()/maxLen); } symbolCounter.setText(str); }else{ symbolCounter.setText(""); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_conversation, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { if(contacts != null && contacts.size() > 1){ MenuItem item = menu.findItem(R.id.action_call); item.setVisible(false); } if(!MyPreferencesManager.isWebSmsEnabled(getBaseContext())){ MenuItem item = menu.findItem(R.id.action_show_balance); item.setVisible(false); } return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if(contacts == null || contacts.size() <= 0) return false; if(item.getItemId() == R.id.action_call){ Intent intent = new Intent(Intent.ACTION_CALL, Uri.fromParts("tel", contacts.get(0).getAddress(), null)); startActivity(intent); return true; }else if(item.getItemId() == R.id.people_in_this_conversation){ Utils.buildContactsDialog(ConversationActivity.this,contacts).show(); return true; }else if(item.getItemId() == R.id.action_show_balance){ Utils.showBalanceDialog(this,smsManager); } return super.onOptionsItemSelected(item); } }