package com.koushikdutta.tabletsms; import java.net.URL; import java.net.URLEncoder; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.Map.Entry; import org.json.JSONArray; import org.json.JSONObject; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnMultiChoiceClickListener; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Email; import android.text.ClipboardManager; import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; import android.widget.ViewSwitcher; import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.MenuItem.OnMenuItemClickListener; import com.koushikdutta.urlimageviewhelper.UrlImageViewHelper; public class MainActivity extends SherlockFragmentActivity { private static final String LOGTAG = "TabletSms"; private static class Message implements Comparable<Message> { String key; String number; long date; String type; String image; String message; boolean unread; @Override public int compareTo(Message another) { return new Long(date).compareTo(another.date); } } public static class Conversation implements Comparable<Conversation> { public Conversation(String number) { this.number = number; } String number; LinkedHashMap<String, Message> messages = new LinkedHashMap<String, Message>(); @Override public int compareTo(Conversation another) { return new Long(another.last).compareTo(last); } boolean unread = false; long last = 0; String lastMessageText = ""; } Conversation mCurrentConversation; long mLastLoaded = System.currentTimeMillis() - 14L * 24L * 60L * 60L * 1000L; Settings mSettings; SQLiteDatabase mDatabase; ArrayAdapter<Conversation> mConversations; ArrayAdapter<Message> mConversation; private Conversation findConversation(String number) { for (int i = 0; i < mConversations.getCount(); i++) { Conversation conversation = mConversations.getItem(i); if (!NumberHelper.areSimilar(conversation.number, number)) continue; return conversation; } return null; } private Conversation findOrStartConversation(String number) { Conversation found = findConversation(number); if (found == null) { found = new Conversation(number); } else { mConversations.remove(found); } mConversations.insert(found, 0); return found; } private void merge(LinkedHashMap<String, Message> newMessages) { boolean addedToCurrent = false; for (Entry<String, Message> entry: newMessages.entrySet()) { String key = entry.getKey(); Message message = entry.getValue(); Conversation found = findOrStartConversation(message.number); Message existing = found.messages.put(key, message); found.last = Math.max(found.last, message.date); found.lastMessageText = message.message; found.unread |= entry.getValue().unread; if (found == mCurrentConversation) { addedToCurrent = true; if (existing != null) { mConversation.remove(existing); } mConversation.add(message); } } if (addedToCurrent) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { scrollToEnd(); } } mConversations.notifyDataSetChanged(); // if the conversation is currently being viewed at the time the messages come in // mark them as read if (isConversationShowing()) markRead(mCurrentConversation); if (mCurrentConversation == null && mConversations.getCount() > 0 && getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { setCurrentConversation(mConversations.getItem(0)); } if (mConversations.getCount() == 0) setEmptyText(R.string.no_messages); else clearEmptyText(); if (newMessages.size() == 1) { ratingPester(); } } private boolean isConversationShowing() { if (mSwitcher.getCurrentView() == null) return true; return mSwitcher.getCurrentView() == mSwitcher.getChildAt(1); } private boolean loading = false; private void load() { if (loading) return; loading = true; ThreadingRunnable.background(new ThreadingRunnable() { public void run() { try { Cursor c = mDatabase.query("sms", null, "date > ?", new String[] { ((Long)mLastLoaded).toString() }, null, null, "date"); final LinkedHashMap<String, Message> newMessages = new LinkedHashMap<String, MainActivity.Message>(); int keyIndex = c.getColumnIndex("key"); int numberIndex = c.getColumnIndex("number"); int dateIndex = c.getColumnIndex("date"); int typeIndex = c.getColumnIndex("type"); int imageIndex = c.getColumnIndex("image"); int messageIndex = c.getColumnIndex("message"); int unreadIndex = c.getColumnIndex("unread"); while (c.moveToNext()) { Message message = new Message(); message.key = c.getString(keyIndex); message.number = c.getString(numberIndex); message.date = c.getLong(dateIndex); message.type = c.getString(typeIndex); message.image = c.getString(imageIndex); message.message = c.getString(messageIndex); message.unread = c.getInt(unreadIndex) != 0; mLastLoaded = Math.max(mLastLoaded, message.date); newMessages.put(message.key, message); } foreground(new Runnable() { @Override public void run() { merge(newMessages); } }); } catch (Exception ex) { } loading = false; }; }); } Hashtable<String, CachedPhoneLookup> mLookup = new Hashtable<String, CachedPhoneLookup>(); private void markRead(Conversation conversation) { if (conversation == null) return; conversation.unread = false; ContentValues vals = new ContentValues(); vals.put("unread", 0); mDatabase.beginTransaction(); try { for (Message message: conversation.messages.values()) { if (!message.unread) continue; message.unread = false; mDatabase.update("sms", vals, "key = ?", new String[] { message.key }); } mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } } ViewSwitcher mSwitcher; ListView mMessages; String mAccount; @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); clearNotifications(); } private void clearNotifications() { if (!mVisible) return; NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); nm.cancel(SyncService.NOTIFICATION_ID); mSettings.setInt("new_message_count", 0); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDatabase = Database.open(this); mSettings = Settings.getInstance(MainActivity.this); mAccount = mSettings.getString("account", null); mVisible = true; clearNotifications(); final String myPhotoUri; if (mAccount != null) { Cursor me = getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[] { Email.CONTACT_ID, Email.DATA1 }, Email.DATA1 + "= ?", new String[] { mAccount }, null); if (me.moveToNext()) { long userId = me.getLong(me.getColumnIndex(Email.CONTACT_ID)); Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, userId); if (uri != null) myPhotoUri = uri.toString(); else myPhotoUri = null; } else { myPhotoUri = null; } me.close(); } else { myPhotoUri = null; } mConversations = new ArrayAdapter<Conversation>(this, R.id.name) { @Override public View getView(int position, View convertView, ViewGroup parent) { View v = (convertView == null) ? getLayoutInflater().inflate(R.layout.contact, null) : convertView; Conversation conversation = getItem(position); ImageView iv = (ImageView)v.findViewById(R.id.image); TextView name = (TextView)v.findViewById(R.id.name); TextView text = (TextView)v.findViewById(R.id.last_message); text.setText(conversation.lastMessageText); CachedPhoneLookup lookup = Helper.getPhoneLookup(MainActivity.this, mLookup, conversation.number); if (lookup != null) { name.setText(lookup.displayName); UrlImageViewHelper.setUrlDrawable(iv, lookup.photoUri, R.drawable.desksms); } else { iv.setImageResource(R.drawable.desksms); name.setText(conversation.number); } if (conversation.number.equals("DeskSMS")) iv.setImageResource(R.drawable.contrast); View unread = v.findViewById(R.id.unread); unread.setVisibility(conversation.unread ? View.VISIBLE : View.INVISIBLE); return v; } }; mConversation = new ArrayAdapter<MainActivity.Message>(this, R.id.incoming_message) { @Override public View getView(int position, View convertView, ViewGroup parent) { View v = (convertView == null) ? getLayoutInflater().inflate(R.layout.message, null) : convertView; Message message = getItem(position); final CachedPhoneLookup lookup = Helper.getPhoneLookup(MainActivity.this, mLookup, message.number); ImageView iv = (ImageView)v.findViewById(R.id.image); ImageView ipic = (ImageView)v.findViewById(R.id.incoming_image); ImageView opic = (ImageView)v.findViewById(R.id.outgoing_image); TextView otext = (TextView)v.findViewById(R.id.outgoing_message); TextView itext = (TextView)v.findViewById(R.id.incoming_message); TextView otime = (TextView)v.findViewById(R.id.outgoing_timestamp); TextView itime = (TextView)v.findViewById(R.id.incoming_timestamp); ImageView pending = (ImageView)v.findViewById(R.id.pending); DateFormat df; if (message.date < System.currentTimeMillis() - 24L * 60L * 60L * 1000L) df = android.text.format.DateFormat.getMediumDateFormat(MainActivity.this); else df = android.text.format.DateFormat.getTimeFormat(MainActivity.this); String date = df.format(new Date(message.date)); if ("incoming".equals(message.type)) { itext.setText(message.message); otext.setVisibility(View.GONE); itext.setVisibility(View.VISIBLE); otime.setVisibility(View.GONE); itime.setVisibility(View.VISIBLE); v.findViewById(R.id.outgoing).setVisibility(View.GONE); v.findViewById(R.id.incoming).setVisibility(View.VISIBLE); itime.setText(date); if (lookup != null) { UrlImageViewHelper.setUrlDrawable(iv, lookup.photoUri, R.drawable.desksms); iv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(lookup.contactUri); // BUG: Why does pressing back in the contact app not take me back to TabletSMS? Annoying. startActivity(intent); } }); } else { iv.setImageResource(R.drawable.desksms); iv.setOnClickListener(null); } } else { otext.setText(message.message); itext.setVisibility(View.GONE); otext.setVisibility(View.VISIBLE); itime.setVisibility(View.GONE); otime.setVisibility(View.VISIBLE); v.findViewById(R.id.incoming).setVisibility(View.GONE); v.findViewById(R.id.outgoing).setVisibility(View.VISIBLE); otime.setText(date); UrlImageViewHelper.setUrlDrawable(iv, myPhotoUri, R.drawable.contact); } if ("pending".equals(message.type) && message.date < System.currentTimeMillis() - 5L * 60L * 1000L) { message.type = "failed"; } pending.setVisibility(View.GONE); if ("pending".equals(message.type)) { pending.setVisibility(View.VISIBLE); pending.setImageResource(R.drawable.ic_sms_mms_pending); } else if ("failed".equals(message.type)) { pending.setVisibility(View.VISIBLE); pending.setImageResource(R.drawable.ic_list_alert_sms_failed); } else { pending.setVisibility(View.GONE); } ipic.setVisibility(View.GONE); opic.setVisibility(View.GONE); if ("true".equals(message.image)) { otext.setVisibility(View.GONE); itext.setVisibility(View.GONE); if ("incoming".equals(message.type)) { ipic.setVisibility(View.VISIBLE); UrlImageViewHelper.setUrlDrawable(ipic, ServiceHelper.IMAGE_URL + "/" + URLEncoder.encode(message.key), R.drawable.placeholder); } else { opic.setVisibility(View.VISIBLE); UrlImageViewHelper.setUrlDrawable(opic, ServiceHelper.IMAGE_URL + "/" + message.key, R.drawable.placeholder); } } if (message.number.equals("DeskSMS")) iv.setImageResource(R.drawable.contrast); return v; } }; mSwitcher = (ViewSwitcher)findViewById(R.id.switcher); final GestureDetector detector = new GestureDetector(this, new SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (Math.abs(velocityX) < Math.abs(velocityY)) return false; if (!isConversationShowing()) { if (velocityX > 0) return false; forward(); return true; } else { if (velocityX < 0) return false; back(); return true; } } }); sendText = (EditText)findViewById(R.id.send_text); ImageButton send = (ImageButton)findViewById(R.id.send); send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendSms(); } }); sendText.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { sendSms(); return true; } }); mCurrentConversationName = (TextView)findViewById(R.id.name); ListView listView = (ListView)findViewById(R.id.list); listView.setAdapter(mConversations); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) { Conversation conversation = mConversations.getItem(position); setCurrentConversation(conversation); } }); listView.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int position, long arg3) { final Conversation conversation = mConversations.getItem(position); AlertDialog.Builder builder = new Builder(MainActivity.this); builder.setItems(new CharSequence[] { getString(R.string.delete) }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == 0) { deleteConversation(conversation); } } }); builder.create().show(); return true; } }); OnTouchListener listener = new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return detector.onTouchEvent(event); } }; listView.setOnTouchListener(listener); mMessages = (ListView)findViewById(R.id.messages); mMessages.setOnTouchListener(listener); mMessages.setAdapter(mConversation); mMessages.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int position, long arg3) { final Message message = mConversation.getItem(position); AlertDialog.Builder builder = new Builder(MainActivity.this); builder.setItems(new CharSequence[] { getText(R.string.copy_text), getString(R.string.delete) }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == 0) { ClipboardManager cb = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); cb.setText(message.message); } else { mCurrentConversation.messages.remove(message.key); mConversation.remove(message); mDatabase.delete("sms", "key = ?", new String[] { message.key }); ThreadingRunnable.background(new ThreadingRunnable() { @Override public void run() { try { String delete = ServiceHelper.SMS_URL + "?operation=DELETE&key=" + URLEncoder.encode(message.key); ServiceHelper.retryExecuteAndDisconnect(MainActivity.this, mAccount, new URL(delete), null); } catch (Exception e) { e.printStackTrace(); } } }); } } }); builder.create().show(); return true; } }); mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mAccount == null) return; load(); clearNotifications(); } }; IntentFilter filter = new IntentFilter("com.koushikdutta.tabletsms.SYNC_COMPLETE"); registerReceiver(mReceiver, filter); View c = findViewById(android.R.id.empty); mEmptyView = (TextView)c.findViewById(R.id.empty); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { mMessages.setEmptyView(c); } else { listView.setEmptyView(c); } c.setId(android.R.id.empty); TileableBackgroundDrawable tile = new TileableBackgroundDrawable(getResources(), R.drawable.stitch); mCurrentConversationName.setBackgroundDrawable(tile); // findViewById(R.id.send_container).setBackgroundDrawable(tile); findViewById(R.id.messages_header).setBackgroundDrawable(tile); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { listView.setBackgroundDrawable(tile); } if (Helper.isJavaScriptNullOrEmpty(mAccount)) { doLogin(); } else { load(); } } private void back() { if (mMenuTrash != null) mMenuTrash.setVisible(false); if (!isConversationShowing()) return; mSwitcher.setInAnimation(AnimationUtils.loadAnimation(MainActivity.this, R.anim.flipper_out)); mSwitcher.setOutAnimation(AnimationUtils.loadAnimation(MainActivity.this, R.anim.flipper_out_fast)); mSwitcher.showPrevious(); getSupportActionBar().setDisplayHomeAsUpEnabled(false); } private void forward() { if (mCurrentConversation == null) return; if (isConversationShowing()) return; if (mMenuTrash != null) mMenuTrash.setVisible(true); mSwitcher.setInAnimation(AnimationUtils.loadAnimation(MainActivity.this, R.anim.flipper_in)); mSwitcher.setOutAnimation(AnimationUtils.loadAnimation(MainActivity.this, R.anim.flipper_in_fast)); mSwitcher.showNext(); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } public void onBackPressed() { if (isConversationShowing() && getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) { back(); return; } super.onBackPressed(); }; static boolean mVisible = false; protected void onResume() { super.onResume(); mVisible = true; clearNotifications(); }; protected void onPause() { super.onPause(); mVisible = false; }; BroadcastReceiver mReceiver; @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { back(); return true; } return super.onOptionsItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getSupportMenuInflater(); inflater.inflate(R.menu.activity_main, menu); mMenuMarkAsRead = menu.getItem(0); mMenuMarkAsRead.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { for (int i = 0; i < mConversations.getCount(); i++) { Conversation conversation = mConversations.getItem(i); markRead(conversation); mConversations.notifyDataSetChanged(); } Toast.makeText(MainActivity.this, R.string.all_messages_read, Toast.LENGTH_SHORT).show(); ThreadingRunnable.background(new ThreadingRunnable() { @Override public void run() { try { ServiceHelper.retryExecuteAndDisconnect(MainActivity.this, mAccount, new URL(ServiceHelper.READ_URL), null); } catch (Exception e) { e.printStackTrace(); } } }); return true; } }); menu.getItem(3).setOnMenuItemClickListener(new OnMenuItemClickListener() { boolean[] options = new boolean[] { mSettings.getBoolean("notifications", true) }; @Override public boolean onMenuItemClick(MenuItem item) { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setMultiChoiceItems(new CharSequence[] { getString(R.string.notifications) }, options, new OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { options[which] = isChecked; } }); builder.setTitle(R.string.settings); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mSettings.setBoolean("notifications", options[0]); } }); builder.create().show(); return true; } }); menu.getItem(4).setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { mAccount = null; mSettings.setString("account", null); mDatabase.delete("sms", null, null); mSettings.setLong("last_sync_timestamp", 0); mLastLoaded = System.currentTimeMillis() - 14L * 24L * 60L * 60L * 1000L; mConversations.clear(); mConversation.clear(); setCurrentConversation(null); doLogin(); return true; } }); menu.getItem(1).setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); startActivityForResult(intent, PICK_CONTACT); return true; } }); mMenuTrash = menu.getItem(2); mMenuTrash.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { deleteConversation(mCurrentConversation); return true; } }); mMenuTrash.setVisible(isConversationShowing()); return super.onCreateOptionsMenu(menu); } MenuItem mMenuMarkAsRead; MenuItem mMenuTrash; private static final int PICK_CONTACT = 10004; @Override protected void onActivityResult(int req, int res, Intent data) { super.onActivityResult(req, res, data); if (res == RESULT_OK && data != null) { if (req == PICK_CONTACT) { Uri uri = data.getData(); if (uri != null) { Cursor c = null; try { c = getContentResolver().query(uri, new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE }, null, null, null); if (c != null && c.moveToFirst()) { String number = c.getString(0); setCurrentConversation(findOrStartConversation(number)); } } finally { if (c != null) { c.close(); } } } } } } private void doLogin() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.welcome); builder.setIcon(R.drawable.icon); builder.setMessage(R.string.welcome_info); builder.setCancelable(false); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { TickleServiceHelper.login(MainActivity.this, new com.koushikdutta.tabletsms.Callback<Boolean>() { @Override public void onCallback(Boolean result) { if (!result) { doLogin(); return; } setEmptyText(R.string.messages_loading); mAccount = mSettings.getString("account", null); Helper.startSync(MainActivity.this); System.out.println(result); } }); } }); builder.create().show(); } @Override protected void onDestroy() { mVisible = false; unregisterReceiver(mReceiver); super.onDestroy(); } private void scrollToEnd() { mMessages.setTranscriptMode(ListView.TRANSCRIPT_MODE_DISABLED); new Handler().postDelayed(new Runnable() { @Override public void run() { mMessages.smoothScrollToPosition(mConversation.getCount() - 1); // mMessages.setSelected(mConversation.getCount() - 1); // mMessages.smoothScrollToPosition(mConversation.getCount() + 1); mMessages.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); } }, 100); } private void ratingPester() { boolean pestered = mSettings.getBoolean("ratings_pestered", false); if (pestered) return; int o = mSettings.getInt("usage_single_outgoing", 0); int i = mSettings.getInt("usage_single_incoming", 0); if (o < 3 || i < 3) return; pestered = true; mSettings.setBoolean("ratings_pestered", true); Message message = new Message(); message.number = "DeskSMS"; message.date = System.currentTimeMillis(); message.key = message.number + "/" + message.date; message.type = "incoming"; message.unread = true; message.message = getString(R.string.rate_me); LinkedHashMap<String, Message> newMessages = new LinkedHashMap<String, MainActivity.Message>(); newMessages.put(message.key, message); merge(newMessages); } EditText sendText; private void sendSms() { String text = sendText.getText().toString(); if (text == null || text.length() == 0 || mCurrentConversation == null) return; int prevCounter = mSettings.getInt("usage_single_outgoing", 0); mSettings.setInt("usage_single_outgoing", prevCounter + 1); ratingPester(); sendText.setText(""); InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); final Message message = new Message(); message.date = System.currentTimeMillis(); message.message = text; message.type = "pending"; message.number = mCurrentConversation.number; message.key = "" + message.number + "/" + message.date; message.unread = false; mCurrentConversation.messages.put(message.key, message); mConversation.add(message); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { scrollToEnd(); } ContentValues insert = new ContentValues(); insert.put("date", message.date); insert.put("message", message.message); insert.put("key", message.key); insert.put("number", message.number); insert.put("type", message.type); insert.put("unread", 0); mDatabase.insert("sms", null, insert); ThreadingRunnable.background(new ThreadingRunnable() { @Override public void run() { final String account = mSettings.getString("account"); try { JSONObject envelope = new JSONObject(); JSONArray data = new JSONArray(); envelope.put("data", data); JSONObject out = new JSONObject(); data.put(out); out.put("date", message.date); out.put("message", message.message); out.put("number", message.number); Log.i(LOGTAG, ServiceHelper.retryExecuteAsString(MainActivity.this, account, new URL(ServiceHelper.OUTBOX_URL), new ServiceHelper.JSONPoster(envelope))); } catch (Exception e) { e.printStackTrace(); foreground(new Runnable() { @Override public void run() { message.type = "failed"; mConversation.notifyDataSetChanged(); } }); } } }); } private void setCurrentConversation(Conversation conversation) { mConversation.clear(); mCurrentConversation = conversation; if (conversation == null) { mCurrentConversationName.setText(null); return; } markRead(conversation); LinkedHashMap<String, Message> messages = conversation.messages; for (Message message: messages.values()) { mConversation.add(message); } CachedPhoneLookup lookup = Helper.getPhoneLookup(MainActivity.this, mLookup, conversation.number); if (lookup != null) mCurrentConversationName.setText(lookup.displayName); else mCurrentConversationName.setText(conversation.number); forward(); mConversations.notifyDataSetChanged(); } TextView mCurrentConversationName; private void deleteConversation(final Conversation conversation) { if (conversation == null) return; confirmDelete(R.string.confirm_delete_conversation, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { deleteConversationInternal(conversation); } }); } private void setFirstAvailableConversation() { if (mConversations.getCount() == 0) { setCurrentConversation(null); return; } setCurrentConversation(mConversations.getItem(0)); } private void deleteConversationInternal(Conversation conversation) { final HashMap<String, ArrayList<Long>> numbers = new HashMap<String, ArrayList<Long>>(); mConversations.remove(conversation); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { if (mCurrentConversation == conversation) setFirstAvailableConversation(); } else { back(); } try { mDatabase.beginTransaction(); for (Message message: conversation.messages.values()) { ArrayList<Long> dates = numbers.get(message.number); if (dates == null) { dates = new ArrayList<Long>(); numbers.put(message.number, dates); } dates.add(message.date); mDatabase.delete("sms", "key = ?", new String[] { message.key }); } mDatabase.setTransactionSuccessful(); } catch (Exception ex) { ex.printStackTrace(); } finally { mDatabase.endTransaction(); } ThreadingRunnable.background(new ThreadingRunnable() { String delete; void doDelete(String u) { try { delete += "0]"; // append a dummy zero to fix the trailing comma ServiceHelper.retryExecuteAndDisconnect(MainActivity.this, mAccount, new URL(delete), null); } catch (Exception e) { e.printStackTrace(); } finally { delete = u; } } @Override public void run() { for (Entry<String, ArrayList<Long>> entry: numbers.entrySet()) { String number = entry.getKey(); final String u = ServiceHelper.DELETE_CONVERSATION_URL + "?number=" + URLEncoder.encode(number) + "&dates=["; delete = u; ArrayList<Long> dates = entry.getValue(); int count = 0; while (dates.size() > 0) { long date = dates.remove(dates.size() - 1); delete += date + ","; count++; if (count == 10) { doDelete(u); count = 0; } } doDelete(u); } } }); } TextView mEmptyView; private void setEmptyText(int resid) { mEmptyView.setText(resid); } private void clearEmptyText() { mEmptyView.setText(null); } private void confirmDelete(int resId, DialogInterface.OnClickListener listener) { new Builder(this) .setMessage(resId) .setPositiveButton(android.R.string.ok, listener) .setNegativeButton(android.R.string.cancel, null) .create().show(); } }