package net.everythingandroid.smspopup.ui;
import java.util.ArrayList;
import java.util.List;
import net.everythingandroid.smspopup.BuildConfig;
import net.everythingandroid.smspopup.R;
import net.everythingandroid.smspopup.controls.FragmentStatePagerAdapter;
import net.everythingandroid.smspopup.controls.QmTextWatcher;
import net.everythingandroid.smspopup.controls.SmsPopupPager;
import net.everythingandroid.smspopup.controls.SmsPopupPager.MessageCountChanged;
import net.everythingandroid.smspopup.preferences.ButtonListPreference;
import net.everythingandroid.smspopup.provider.SmsMmsMessage;
import net.everythingandroid.smspopup.provider.SmsPopupContract.QuickMessages;
import net.everythingandroid.smspopup.receiver.ClearAllReceiver;
import net.everythingandroid.smspopup.service.ReminderService;
import net.everythingandroid.smspopup.service.SmsPopupUtilsService;
import net.everythingandroid.smspopup.ui.SmsPopupFragment.SmsPopupButtonsListener;
import net.everythingandroid.smspopup.util.Eula;
import net.everythingandroid.smspopup.util.Log;
import net.everythingandroid.smspopup.util.ManageKeyguard;
import net.everythingandroid.smspopup.util.ManageKeyguard.LaunchOnKeyguardExit;
import net.everythingandroid.smspopup.util.ManageNotification;
import net.everythingandroid.smspopup.util.ManagePreferences.Defaults;
import net.everythingandroid.smspopup.util.ManageWakeLock;
import net.everythingandroid.smspopup.util.RetainFragment;
import net.everythingandroid.smspopup.util.SmsPopupUtils;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.util.LruCache;
import android.support.v4.view.PagerAdapter;
import android.telephony.PhoneNumberUtils;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import com.commonsware.cwac.wakeful.WakefulIntentService;
import com.viewpagerindicator.CirclePageIndicator;
public class SmsPopupActivity extends FragmentActivity implements SmsPopupButtonsListener {
private boolean exitingKeyguardSecurely = false;
private SharedPreferences mPrefs;
private InputMethodManager inputManager;
private View inputView;
private EditText qrEditText;
private ProgressDialog mProgressDialog;
private SmsPopupPager smsPopupPager;
private SmsPopupPagerAdapter smsPopupPagerAdapter;
private CirclePageIndicator pagerIndicator;
private LruCache<Uri, Bitmap> mBitmapCache = null;
private boolean wasVisible = false;
private boolean replying = false;
private boolean inbox = false;
private int privacyMode;
private boolean privacyAlways = false;
private boolean showUnlockButton = false;
private boolean showButtons = true;
private String signatureText;
private boolean hasNotified = false;
private static final int DIALOG_DELETE = Menu.FIRST;
private static final int DIALOG_QUICKREPLY = Menu.FIRST + 1;
private static final int DIALOG_PRESET_MSG = Menu.FIRST + 2;
private static final int DIALOG_LOADING = Menu.FIRST + 3;
private static final int CONTEXT_CLOSE_ID = Menu.FIRST;
private static final int CONTEXT_DELETE_ID = Menu.FIRST + 1;
private static final int CONTEXT_REPLY_ID = Menu.FIRST + 2;
private static final int CONTEXT_QUICKREPLY_ID = Menu.FIRST + 3;
private static final int CONTEXT_INBOX_ID = Menu.FIRST + 4;
private static final int CONTEXT_TTS_ID = Menu.FIRST + 5;
private static final int CONTEXT_VIEWCONTACT_ID = Menu.FIRST + 6;
private static final int VOICE_RECOGNITION_REQUEST_CODE = 8888;
private static final int BITMAP_CACHE_SIZE = 8;
private TextView quickreplyTextView;
private SmsMmsMessage quickReplySmsMessage;
private Cursor mCursor = null;
private int[] buttonTypes;
private TextToSpeech androidTts = null;
/*
* *****************************************************************************
* Main onCreate override
* *****************************************************************************
*/
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.popup);
setupPreferences();
setupViews();
if (bundle == null) { // new activity
initializeMessagesAndWake(getIntent().getExtras());
} else { // this activity was recreated after being destroyed
initializeMessagesAndWake(bundle);
}
Eula.show(this);
}
/*
* *****************************************************************************
* Setup methods - these will mostly be run one time only
* *****************************************************************************
*/
private void setupPreferences() {
// Get shared prefs
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
// Check if screen orientation should be "user" or "behind" based on prefs
if (mPrefs.getBoolean(getString(R.string.pref_autorotate_key), Defaults.PREFS_AUTOROTATE)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
}
// Fetch privacy mode
final boolean privacyMessage =
mPrefs.getBoolean(getString(R.string.pref_privacy_key), Defaults.PREFS_PRIVACY);
final boolean privacySender = mPrefs.getBoolean(getString(R.string.pref_privacy_sender_key),
Defaults.PREFS_PRIVACY_SENDER);
privacyAlways = mPrefs.getBoolean(getString(R.string.pref_privacy_always_key),
Defaults.PREFS_PRIVACY_ALWAYS);
if (privacySender && privacyMessage) {
privacyMode = SmsPopupFragment.PRIVACY_MODE_HIDE_ALL;
} else if (privacyMessage) {
privacyMode = SmsPopupFragment.PRIVACY_MODE_HIDE_MESSAGE;
} else {
privacyMode = SmsPopupFragment.PRIVACY_MODE_OFF;
}
showUnlockButton = mPrefs.getBoolean(
getString(R.string.pref_useUnlockButton_key), Defaults.PREFS_USE_UNLOCK_BUTTON);
// Fetch quick reply signature
signatureText = mPrefs.getString(getString(R.string.pref_notif_signature_key), "");
if (signatureText.length() > 0)
signatureText = " " + signatureText;
}
private void setupViews() {
// Find main views
smsPopupPager = (SmsPopupPager) findViewById(R.id.SmsPopupPager);
smsPopupPagerAdapter = new SmsPopupPagerAdapter(getSupportFragmentManager());
smsPopupPager.setAdapter(smsPopupPagerAdapter);
pagerIndicator = (CirclePageIndicator) findViewById(R.id.indicator);
pagerIndicator.setViewPager(smsPopupPager);
smsPopupPager.setIndicator(pagerIndicator);
smsPopupPager.setGestureListener(new SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
smsPopupPager.showContextMenu();
}
});
registerForContextMenu(smsPopupPager);
RetainFragment mRetainFragment =
RetainFragment.findOrCreateRetainFragment(getSupportFragmentManager());
mBitmapCache = (LruCache<Uri, Bitmap>) mRetainFragment.getObject();
// Init cache
if (mBitmapCache == null) {
mBitmapCache = new LruCache<Uri, Bitmap>(BITMAP_CACHE_SIZE);
mRetainFragment.setObject(mBitmapCache);
}
smsPopupPager.setOnMessageCountChanged(new MessageCountChanged() {
@Override
public void onChange(int current, int total) {
if (total == 1) {
pagerIndicator.setVisibility(View.INVISIBLE);
} else if (total >= 2) {
pagerIndicator.setVisibility(View.VISIBLE);
}
if (hasNotified) {
ManageNotification.update(SmsPopupActivity.this,
smsPopupPager.getMessage(current), total);
}
}
});
// See if user wants to show buttons on the popup
if (!mPrefs.getBoolean(getString(R.string.pref_show_buttons_key),
Defaults.PREFS_SHOW_BUTTONS)) {
showButtons = false;
} else {
buttonTypes = new int[] {
Integer.parseInt(mPrefs.getString(
getString(R.string.pref_button1_key), Defaults.PREFS_BUTTON1)),
Integer.parseInt(mPrefs.getString(
getString(R.string.pref_button2_key), Defaults.PREFS_BUTTON2)),
Integer.parseInt(mPrefs.getString(
getString(R.string.pref_button3_key), Defaults.PREFS_BUTTON3)),
};
}
refreshViews();
resizeLayout();
}
private void initializeMessagesAndWake(Bundle b) {
initializeMessagesAndWake(b, false);
}
/**
* Setup messages within the popup given an intent bundle
*
* @param b
* the incoming intent bundle
* @param newIntent
* if this is from onNewIntent or not
*/
private void initializeMessagesAndWake(Bundle b, boolean newIntent) {
// Create message from bundle
SmsMmsMessage message = new SmsMmsMessage(getApplicationContext(), b);
message.locateMessageId();
if (newIntent) {
smsPopupPager.addMessage(message);
wakeApp();
} else {
if (message != null) {
new LoadUnreadMessagesAsyncTask().execute(message);
}
}
}
private class LoadUnreadMessagesAsyncTask extends AsyncTask<SmsMmsMessage,
Void, ArrayList<SmsMmsMessage>> {
ProgressBar mProgressBar;
@Override
protected void onPreExecute() {
mProgressBar = (ProgressBar) findViewById(R.id.progress);
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
protected ArrayList<SmsMmsMessage> doInBackground(SmsMmsMessage... args) {
final SmsMmsMessage newMessage = args[0];
// Get all unread messages
ArrayList<SmsMmsMessage> messages =
SmsPopupUtils.getUnreadMessages(SmsPopupActivity.this);
if (messages == null) {
// If no messages found, add the new message and we're done
messages = new ArrayList<SmsMmsMessage>(1);
messages.add(newMessage);
} else {
// Otherwise we now have an array of unread messages
final long messageId = newMessage.getMessageId();
// We have to deal with a system race condition now, what if the new message isn't
// yet in the system database? In that case, messageId will be 0 still and we need
// to manually add the message to our list.
if (messageId == 0) {
// Add the new message as it wouldn't have been found in the system database
messages.add(newMessage);
} else {
// Otherwise we need to check if getUnreadMessages() found the new message,
// there's a chance it didn't.
boolean found = false;
for (int i = 0; i < messages.size(); i++) {
if (messages.get(i).getMessageId() == messageId) {
// It was found in the getUnreadMessages() query
found = true;
messages.get(i).setNotify(true);
}
}
if (!found) {
// If it wasn't found, add it manually to the list
messages.add(newMessage);
}
}
}
return messages;
}
@Override
protected void onPostExecute(ArrayList<SmsMmsMessage> result) {
mProgressBar.setVisibility(View.GONE);
smsPopupPager.addMessages(result);
smsPopupPager.showLast();
wakeApp();
}
}
/*
* *****************************************************************************
* Methods that will be called several times throughout the life of the activity
* *****************************************************************************
*/
private void refreshViews() {
ManageKeyguard.initialize(this);
if ((ManageKeyguard.inKeyguardRestrictedInputMode() && showUnlockButton)
|| privacyMode != SmsPopupFragment.PRIVACY_MODE_OFF) {
unregisterForContextMenu(smsPopupPager);
} else {
showUnlockButton = false;
// Enable long-press context menu
registerForContextMenu(smsPopupPager);
smsPopupPagerAdapter.unlockScreen();
}
}
private void resizeLayout() {
final int width = (int) getResources().getDimension(R.dimen.smspopup_pager_width);
final int height = (int) getResources().getDimension(R.dimen.smspopup_pager_height);
final int screenWidth = getResources().getDisplayMetrics().widthPixels;
final int bottom_padding = (int) getResources().getDimension(R.dimen.smspopup_bottom_margin);
final View marginView = findViewById(R.id.popup_bottom_margin_view);
LinearLayout.LayoutParams marginParams =
(LinearLayout.LayoutParams) marginView.getLayoutParams();
marginParams.height = bottom_padding;
marginView.setLayoutParams(marginParams);
smsPopupPagerAdapter.resizeFragments(width, screenWidth);
RelativeLayout.LayoutParams params =
(RelativeLayout.LayoutParams) smsPopupPager.getLayoutParams();
params.height = height;
smsPopupPager.setLayoutParams(params);
smsPopupPager.invalidate();
}
/**
* Wake up the activity, this will acquire the wakelock (turn on the screen) and sound the
* notification if needed. This is called once all preparation is done for this activity (end
* of onCreate()).
*/
private void wakeApp() {
// Time to acquire a full WakeLock (turn on screen)
ManageWakeLock.acquireFull(getApplicationContext());
ManageWakeLock.releasePartial();
replying = false;
inbox = false;
SmsMmsMessage notifyMessage = smsPopupPager.shouldNotify();
// See if a notification is needed for this set of messages
if (notifyMessage != null) {
// Schedule a reminder notification
ReminderService.scheduleReminder(this, notifyMessage);
// Run the notification
ManageNotification.show(this, notifyMessage, smsPopupPager.getPageCount());
hasNotified = true;
}
}
/**
* Customized activity finish. Ensures the notification is in sync and cancels any scheduled
* reminders (as the user has interrupted the app).
*/
private void myFinish() {
if (BuildConfig.DEBUG)
Log.v("myFinish()");
if (inbox) {
ManageNotification.clearAll(getApplicationContext());
} else {
// Start a service that will update the notification in the status bar
Intent i = new Intent(getApplicationContext(), SmsPopupUtilsService.class);
i.setAction(SmsPopupUtilsService.ACTION_UPDATE_NOTIFICATION);
if (replying) {
// Convert current message to bundle
i.putExtras(smsPopupPager.getActiveMessage().toBundle());
// We need to know if the user is replying - if so, the entire thread id should
// be ignored when working out the message tally in the notification bar.
// We can't rely on the system database as it may take a little while for the
// reply intent to fire and load up the messaging up (after which the messages
// will be marked read in the database).
i.putExtra(SmsMmsMessage.EXTRAS_REPLYING, replying);
}
// Start the service
WakefulIntentService.sendWakefulWork(getApplicationContext(), i);
}
// Cancel any reminder notifications
ReminderService.cancelReminder(getApplicationContext());
// Finish up the activity
finish();
}
/*
* *****************************************************************************
* Method overrides from Activity class
* *****************************************************************************
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onNewIntent()");
hasNotified = false;
// Update intent held by activity
setIntent(intent);
// Setup messages
initializeMessagesAndWake(intent.getExtras(), true);
}
@Override
protected void onStart() {
super.onStart();
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onStart()");
// ManageWakeLock.acquirePartial(getApplicationContext());
}
@Override
protected void onResume() {
super.onResume();
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onResume()");
wasVisible = false;
// Reset exitingKeyguardSecurely bool to false
exitingKeyguardSecurely = false;
}
@Override
protected void onPause() {
super.onPause();
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onPause()");
// Hide the soft keyboard in case it was shown via quick reply
hideSoftKeyboard();
// Shutdown Android TTS
if (androidTts != null) {
androidTts.shutdown();
}
// Dismiss loading dialog
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
if (wasVisible) {
// Cancel the receiver that will clear our locks
ClearAllReceiver.removeCancel(getApplicationContext());
ClearAllReceiver.clearAll(!exitingKeyguardSecurely);
}
}
@Override
protected void onStop() {
super.onStop();
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onStop()");
// Cancel the receiver that will clear our locks
ClearAllReceiver.removeCancel(getApplicationContext());
ClearAllReceiver.clearAll(!exitingKeyguardSecurely);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
/**
* Create Dialog
*/
@Override
protected Dialog onCreateDialog(int id) {
if (BuildConfig.DEBUG)
Log.v("onCreateDialog()");
switch (id) {
/*
* Delete message dialog
*/
case DIALOG_DELETE:
return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(getString(R.string.pref_show_delete_button_dialog_title))
.setMessage(getString(R.string.pref_show_delete_button_dialog_text))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
deleteMessage();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
/*
* Quick Reply Dialog
*/
case DIALOG_QUICKREPLY:
LayoutInflater factory = getLayoutInflater();
final View qrLayout = factory.inflate(R.layout.message_quick_reply, null);
qrEditText = (EditText) qrLayout.findViewById(R.id.QuickReplyEditText);
final TextView qrCounterTextView =
(TextView) qrLayout.findViewById(R.id.QuickReplyCounterTextView);
final Button qrSendButton = (Button) qrLayout.findViewById(R.id.send_button);
final ImageButton voiceRecognitionButton =
(ImageButton) qrLayout.findViewById(R.id.SpeechRecogButton);
voiceRecognitionButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
// Check if the device has the ability to do speech
// recognition
final PackageManager packageManager = SmsPopupActivity.this.getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(intent, 0);
if (list.size() > 0) {
// TODO: really should allow voice input here without unlocking first
// (quick replies without unlock are OK anyway)
exitingKeyguardSecurely = true;
ManageKeyguard.exitKeyguardSecurely(new LaunchOnKeyguardExit() {
@Override
public void LaunchOnKeyguardExitSuccess() {
SmsPopupActivity.this.startActivityForResult(
intent, VOICE_RECOGNITION_REQUEST_CODE);
}
});
} else {
Toast.makeText(SmsPopupActivity.this, R.string.error_no_voice_recognition,
Toast.LENGTH_LONG).show();
view.setEnabled(false);
}
}
});
qrEditText.addTextChangedListener(new QmTextWatcher(this, qrCounterTextView,
qrSendButton));
qrEditText.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// event != null means enter key pressed
if (event != null) {
// if shift is not pressed then move focus to send button
if (!event.isShiftPressed()) {
if (v != null) {
View focusableView = v.focusSearch(View.FOCUS_RIGHT);
if (focusableView != null) {
focusableView.requestFocus();
return true;
}
}
}
// otherwise allow keypress through
return false;
}
if (actionId == EditorInfo.IME_ACTION_SEND) {
if (v != null) {
sendQuickReply(v.getText().toString());
}
return true;
}
// else consume
return true;
}
});
quickreplyTextView = (TextView) qrLayout.findViewById(R.id.QuickReplyTextView);
QmTextWatcher.getQuickReplyCounterText(
qrEditText.getText().toString(),
qrCounterTextView,
qrSendButton);
qrSendButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
sendQuickReply(qrEditText.getText().toString());
}
});
// Construct basic AlertDialog using AlertDialog.Builder
final AlertDialog qrAlertDialog = new AlertDialog.Builder(this)
.setIcon(android.R.drawable.ic_dialog_email)
.setTitle(R.string.quickreply_title)
.create();
// Set the custom layout with no spacing at the bottom
qrAlertDialog.setView(qrLayout, 0, SmsPopupUtils.pixelsToDip(getResources(), 5), 0, 0);
qrAlertDialog.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
storeQuickReplyText(qrEditText.getText().toString());
removeDialog(DIALOG_QUICKREPLY);
}
});
// Preset messages button
Button presetButton = (Button) qrLayout.findViewById(R.id.PresetMessagesButton);
presetButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showDialog(DIALOG_PRESET_MSG);
}
});
// Cancel button
Button cancelButton = (Button) qrLayout.findViewById(R.id.CancelButton);
cancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (qrAlertDialog != null) {
hideSoftKeyboard();
qrAlertDialog.dismiss();
removeDialog(DIALOG_QUICKREPLY);
}
}
});
// Ensure this dialog is counted as "editable" (so soft keyboard
// will always show on top)
qrAlertDialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
qrAlertDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (BuildConfig.DEBUG)
Log.v("Quick Reply Dialog: onDissmiss()");
storeQuickReplyText(qrEditText.getText().toString());
}
});
// Update quick reply views now that they have been created
if (quickReplySmsMessage != null) {
updateQuickReplyView(quickReplySmsMessage.getReplyText());
} else {
updateQuickReplyView("");
}
return qrAlertDialog;
/*
* Preset messages dialog
*/
case DIALOG_PRESET_MSG:
mCursor = getContentResolver().query(QuickMessages.CONTENT_URI, null, null, null, null);
startManagingCursor(mCursor);
AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(this)
.setIcon(android.R.drawable.ic_dialog_email)
.setTitle(R.string.pref_message_presets_title);
// If user has some presets defined ...
if (mCursor != null && mCursor.getCount() > 0) {
mDialogBuilder.setCursor(mCursor, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int item) {
if (BuildConfig.DEBUG)
Log.v("Item clicked = " + item);
mCursor.moveToPosition(item);
quickReply(mCursor.getString(
mCursor.getColumnIndexOrThrow(QuickMessages.QUICKMESSAGE)));
}
}, QuickMessages.QUICKMESSAGE);
} else { // Otherwise display a placeholder as user has no presets
MatrixCursor emptyCursor =
new MatrixCursor(new String[] { QuickMessages._ID,
QuickMessages.QUICKMESSAGE });
emptyCursor.addRow(new String[] { "0",
getString(R.string.message_presets_empty_text) });
mDialogBuilder.setCursor(emptyCursor, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int item) {}
}, QuickMessages.QUICKMESSAGE);
}
return mDialogBuilder.create();
/*
* Loading Dialog
*/
case DIALOG_LOADING:
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage(getString(R.string.loading_message));
mProgressDialog.setIndeterminate(true);
mProgressDialog.setCancelable(true);
return mProgressDialog;
}
return null;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
if (BuildConfig.DEBUG)
Log.v("onPrepareDialog()");
// User interacted so remove all locks and cancel reminders
ClearAllReceiver.removeCancel(getApplicationContext());
ClearAllReceiver.clearAll(false);
ReminderService.cancelReminder(getApplicationContext());
switch (id) {
case DIALOG_QUICKREPLY:
// Update dialog width to same size as popup message, this is incase the screen
// orientation changes and the dialog is left filling the whole width of the screen.
final LayoutParams quickreplyLP = dialog.getWindow().getAttributes();
quickreplyLP.width = (int) getResources().getDimension(R.dimen.smspopup_pager_width);
dialog.getWindow().setAttributes(quickreplyLP);
showSoftKeyboard(qrEditText);
break;
case DIALOG_PRESET_MSG:
// Update dialog width to same size as popup message, this is incase the screen
// orientation changes and the dialog is left filling the whole width of the screen.
final LayoutParams presetLP = dialog.getWindow().getAttributes();
presetLP.width = (int) getResources().getDimension(R.dimen.smspopup_pager_width);
dialog.getWindow().setAttributes(presetLP);
break;
}
}
/**
* Handle the results from the recognition activity.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (BuildConfig.DEBUG)
Log.v("onActivityResult");
if (requestCode == VOICE_RECOGNITION_REQUEST_CODE && resultCode == RESULT_OK) {
ArrayList<String> matches =
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
if (BuildConfig.DEBUG)
Log.v("Voice recog text: " + matches.get(0));
quickReply(matches.get(0));
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onWindowFocusChanged(" + hasFocus + ")");
if (hasFocus) {
// This is really hacky, basically a flag that is set if the message was at some
// point visible. I tried using onResume() or other methods to prevent doing some
// things 2 times but this seemed to be the only reliable way (?)
wasVisible = true;
refreshViews();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onSaveInstanceState()");
// Save values from most recent bundle (ie. most recent message)
// outState.putAll(bundle);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onRestoreInstanceState()");
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (BuildConfig.DEBUG)
Log.v("SMSPopupActivity: onConfigurationChanged()");
resizeLayout();
}
/**
* Create Context Menu (Long-press menu)
*/
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(Menu.NONE, CONTEXT_VIEWCONTACT_ID, Menu.NONE, getString(R.string.view_contact));
menu.add(Menu.NONE, CONTEXT_CLOSE_ID, Menu.NONE, getString(R.string.button_close));
menu.add(Menu.NONE, CONTEXT_DELETE_ID, Menu.NONE, getString(R.string.button_delete));
menu.add(Menu.NONE, CONTEXT_REPLY_ID, Menu.NONE, getString(R.string.button_reply));
menu.add(Menu.NONE, CONTEXT_QUICKREPLY_ID, Menu.NONE, getString(R.string.button_quickreply));
menu.add(Menu.NONE, CONTEXT_TTS_ID, Menu.NONE, getString(R.string.button_tts));
menu.add(Menu.NONE, CONTEXT_INBOX_ID, Menu.NONE, getString(R.string.button_inbox));
}
/**
* Context Menu Item Selected
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case CONTEXT_CLOSE_ID:
closeMessage();
break;
case CONTEXT_DELETE_ID:
showDialog(DIALOG_DELETE);
break;
case CONTEXT_REPLY_ID:
replyToMessage();
break;
case CONTEXT_QUICKREPLY_ID:
quickReply();
break;
case CONTEXT_INBOX_ID:
gotoInbox();
break;
case CONTEXT_TTS_ID:
speakMessage();
break;
case CONTEXT_VIEWCONTACT_ID:
viewContact();
break;
}
return super.onContextItemSelected(item);
}
// The Android text-to-speech library OnInitListener (via wrapper class)
private final OnInitListener androidTtsListener = new OnInitListener() {
@Override
public void onInit(int status) {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
if (status == TextToSpeech.SUCCESS) {
speakMessage();
} else {
Toast.makeText(SmsPopupActivity.this, R.string.error_message, Toast.LENGTH_SHORT);
}
}
};
/*
* *****************************************************************************
* Methods to handle messages (speak, close, reply, quick reply etc.)
* *****************************************************************************
*/
/**
* Speak the message out loud using text-to-speech (either via Android text-to-speech or via the
* free eyes-free text-to-speech library)
*/
private void speakMessage() {
// TODO: we should really require the keyguard be unlocked here if we are in privacy mode
// If not previously initialized...
if (androidTts == null) {
// Show a loading dialog
showDialog(DIALOG_LOADING);
// User interacted so remove all locks and cancel reminders
ClearAllReceiver.removeCancel(getApplicationContext());
ClearAllReceiver.clearAll(false);
ReminderService.cancelReminder(getApplicationContext());
// We'll use update notification to stop the sound playing
ManageNotification.update(
this, smsPopupPager.getActiveMessage(), smsPopupPager.getPageCount());
androidTts = new TextToSpeech(SmsPopupActivity.this, androidTtsListener);
} else {
androidTts.speak(smsPopupPager.getActiveMessage().getMessageBody(),
TextToSpeech.QUEUE_FLUSH, null);
}
}
/**
* Close the message window/popup, mark the message read if the user has this option on
*/
private void closeMessage() {
Intent i = new Intent(getApplicationContext(), SmsPopupUtilsService.class);
/*
* Switched back to mark messageId as read for >v1.0.6 (marking thread as read is slow for
* really large threads)
*/
i.setAction(SmsPopupUtilsService.ACTION_MARK_MESSAGE_READ);
i.putExtras(smsPopupPager.getActiveMessage().toBundle());
WakefulIntentService.sendWakefulWork(getApplicationContext(), i);
removeActiveMessage();
}
/**
* Reply to the current message, start the reply intent
*/
private void replyToMessage(final SmsMmsMessage message, final boolean replyToThread) {
exitingKeyguardSecurely = true;
ManageKeyguard.exitKeyguardSecurely(new LaunchOnKeyguardExit() {
@Override
public void LaunchOnKeyguardExitSuccess() {
startActivity(message.getReplyIntent(replyToThread));
replying = true;
myFinish();
}
});
}
private void replyToMessage(SmsMmsMessage message) {
replyToMessage(message, true);
}
private void replyToMessage(boolean replyToThread) {
replyToMessage(smsPopupPager.getActiveMessage(), replyToThread);
}
private void replyToMessage() {
replyToMessage(smsPopupPager.getActiveMessage());
}
/**
* View the private message (this basically just unlocks the keyg uard and then updates the
* privacy of the messages).
*/
private void unlockScreen() {
exitingKeyguardSecurely = true;
ManageKeyguard.exitKeyguardSecurely(new LaunchOnKeyguardExit() {
@Override
public void LaunchOnKeyguardExitSuccess() {
SmsPopupActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
setPrivacy(SmsPopupFragment.PRIVACY_MODE_OFF);
}
});
}
});
}
/**
* Take the user to the messaging app inbox
*/
private void gotoInbox() {
exitingKeyguardSecurely = true;
ManageKeyguard.exitKeyguardSecurely(new LaunchOnKeyguardExit() {
@Override
public void LaunchOnKeyguardExitSuccess() {
Intent i = SmsPopupUtils.getSmsInboxIntent();
SmsPopupActivity.this.getApplicationContext().startActivity(i);
inbox = true;
myFinish();
}
});
}
/**
* Delete the current message from the system database
*/
private void deleteMessage() {
Intent i = new Intent(SmsPopupActivity.this.getApplicationContext(),
SmsPopupUtilsService.class);
i.setAction(SmsPopupUtilsService.ACTION_DELETE_MESSAGE);
i.putExtras(smsPopupPager.getActiveMessage().toBundle());
WakefulIntentService.sendWakefulWork(getApplicationContext(), i);
removeActiveMessage();
}
/**
* Sends the actual quick reply message
*/
private void sendQuickReply(String quickReplyMessage) {
hideSoftKeyboard();
if (quickReplyMessage != null) {
if (quickReplyMessage.length() > 0) {
Intent i = new Intent(SmsPopupActivity.this.getApplicationContext(),
SmsPopupUtilsService.class);
i.setAction(SmsPopupUtilsService.ACTION_QUICKREPLY);
i.putExtras(quickReplySmsMessage.toBundle());
i.putExtra(SmsMmsMessage.EXTRAS_QUICKREPLY, quickReplyMessage);
if (BuildConfig.DEBUG)
Log.v("Sending message to " + quickReplySmsMessage.getContactName());
WakefulIntentService.sendWakefulWork(getApplicationContext(), i);
Toast.makeText(this, R.string.quickreply_sending_toast, Toast.LENGTH_LONG).show();
dismissDialog(DIALOG_QUICKREPLY);
removeActiveMessage();
} else {
Toast.makeText(this, R.string.quickreply_nomessage_toast, Toast.LENGTH_LONG).show();
}
}
}
/**
* Show the quick reply dialog, resetting the text in the edittext and storing the current
* SmsMmsMessage (in case another message comes in)
*/
private void quickReply() {
quickReplySmsMessage = smsPopupPager.getActiveMessage();
quickReply(quickReplySmsMessage.getReplyText());
}
/**
* Show the quick reply dialog, if text passed is null or empty then store the current
* SmsMmsMessage (in case another message comes in)
*/
private void quickReply(String text) {
// If this is a MMS or a SMS from email gateway then use regular reply
if (quickReplySmsMessage != null) {
if (quickReplySmsMessage.isMms() || quickReplySmsMessage.isEmail()) {
replyToMessage();
} else { // Else show the quick reply dialog
updateQuickReplyView(text);
showDialog(DIALOG_QUICKREPLY);
}
}
}
/**
* View contact that has the message address (or create if it doesn't exist)
*/
private void viewContact() {
Intent contactIntent = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT);
final String address = smsPopupPager.getActiveMessage().getAddress();
final boolean fromEmail = smsPopupPager.getActiveMessage().isEmail();
if (address != null) {
if (PhoneNumberUtils.isWellFormedSmsAddress(address)) {
contactIntent.setData(Uri.fromParts("tel", address, null));
} else if (fromEmail) {
contactIntent.setData(Uri.fromParts("mailto", address, null));
}
}
startActivity(contactIntent);
}
/**
* Refresh the quick reply view - update the edittext and the counter
*/
private void updateQuickReplyView(String editText) {
if (BuildConfig.DEBUG) Log.v("updateQuickReplyView - '" + editText + "'");
if (qrEditText != null && editText != null) {
qrEditText.setText(editText + signatureText);
qrEditText.setSelection(editText.length());
}
if (quickreplyTextView != null && quickReplySmsMessage != null) {
quickreplyTextView.setText(getString(R.string.quickreply_from_text,
quickReplySmsMessage.getContactName()));
}
}
private void storeQuickReplyText(String text) {
if (text != null) {
// Remove signature if present
if (signatureText != null && !"".equals(signatureText) &&
text.endsWith(signatureText)) {
smsPopupPager.getActiveMessage().setReplyText(
text.substring(0, text.lastIndexOf(signatureText)));
} else {
smsPopupPager.getActiveMessage().setReplyText(text);
}
}
}
/**
* Removes the active message
*/
private void removeActiveMessage() {
final int status = smsPopupPager.removeActiveMessage();
if (status == SmsPopupPager.STATUS_NO_MESSAGES_REMAINING) {
myFinish();
}
}
/*
* *****************************************************************************
* Misc methods
* *****************************************************************************
*/
/**
* Show the soft keyboard and store the view that triggered it
*/
private void showSoftKeyboard(View triggerView) {
if (BuildConfig.DEBUG) Log.v("showSoftKeyboard()");
if (inputManager == null) {
inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
}
inputView = triggerView;
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
/**
* Hide the soft keyboard
*/
private void hideSoftKeyboard() {
if (inputView == null)
return;
if (BuildConfig.DEBUG) Log.v("hideSoftKeyboard()");
if (inputManager == null) {
inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
}
inputManager.hideSoftInputFromWindow(inputView.getApplicationWindowToken(), 0);
inputView = null;
}
private class SmsPopupPagerAdapter extends FragmentStatePagerAdapter {
public SmsPopupPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return smsPopupPager.getPageCount();
}
@Override
public Fragment getItem(int position) {
return SmsPopupFragment.newInstance(
smsPopupPager.getMessage(position), buttonTypes, privacyMode,
showUnlockButton, showButtons);
}
public void setPrivacy(int privacyMode) {
for (int i=0; i<mFragments.size(); i++) {
if (mFragments.get(i) != null) {
((SmsPopupFragment) mFragments.get(i)).setPrivacy(privacyMode);
}
}
}
public void unlockScreen() {
for (int i=0; i<mFragments.size(); i++) {
if (mFragments.get(i) != null) {
((SmsPopupFragment) mFragments.get(i)).setShowUnlockButton(false);
}
}
}
@Override
public int getItemPosition(Object object) {
int idx = smsPopupPager.getMessages().indexOf(object);
if (idx == -1) {
return PagerAdapter.POSITION_NONE;
}
return idx;
}
public void resizeFragments(int width, int height) {
for (int i=0; i<mFragments.size(); i++) {
if (mFragments.get(i) != null) {
((SmsPopupFragment) mFragments.get(i)).resizeLayout(width, height);
}
}
}
}
private void setPrivacy(int mode) {
privacyMode = mode;
smsPopupPagerAdapter.setPrivacy(privacyMode);
refreshViews();
}
@Override
public void onButtonClicked(int buttonType) {
switch (buttonType) {
case ButtonListPreference.BUTTON_DISABLED: // Disabled
break;
case ButtonListPreference.BUTTON_CLOSE: // Close
closeMessage();
break;
case ButtonListPreference.BUTTON_DELETE: // Delete
showDialog(DIALOG_DELETE);
break;
case ButtonListPreference.BUTTON_DELETE_NO_CONFIRM:
// Delete no confirmation
deleteMessage();
break;
case ButtonListPreference.BUTTON_REPLY: // Reply
replyToMessage(true);
break;
case ButtonListPreference.BUTTON_QUICKREPLY: // Quick Reply
quickReply();
break;
case ButtonListPreference.BUTTON_REPLY_BY_ADDRESS: // Quick Reply
replyToMessage(false);
break;
case ButtonListPreference.BUTTON_INBOX: // Inbox
gotoInbox();
break;
case ButtonListPreference.BUTTON_TTS: // Text-to-Speech
speakMessage();
break;
case SmsPopupFragment.BUTTON_VIEW:
unlockScreen();
break;
case SmsPopupFragment.BUTTON_VIEW_MMS:
replyToMessage();
break;
case SmsPopupFragment.BUTTON_UNLOCK:
unlockScreen();
break;
}
}
@Override
public LruCache<Uri, Bitmap> getCache() {
return mBitmapCache;
}
}